【做题记录】DP 杂题
P2577 [ZJOI2004]午餐
$\texttt{solution}$想到貪心:
吃飯慢的先打飯節約時間, 所以先將人按吃飯時間從大到小排序。
狀態:
\(f[i][j]\) 表示前 \(i\) 個人,在 \(1號\) 窗口打飯總時間 \(j\) ,最早吃完飯的時間。
我們可以發現 \(j+k\) 等于前 \(i\) 個人打飯總和( \(sum[i]\) ),\(k = sum(i)-j\) ,所以可以省去一維。
轉移:
核心代碼:
bool cmp(Data x,Data y){ return x.b>y.b; }sort(p+1,p+n+1,cmp); for(int i=1;i<=n;i++) sum[i]=sum[i-1]+p[i].a; memset(dp,inf,sizeof(dp)); dp[0][0]=0; for(int i=1;i<=n;i++) {for(int j=0;j<=sum[i];j++){if(j>=p[i].a) dp[i][j]=min(dp[i][j],max(dp[i-1][j-p[i].a],j+p[i].b));dp[i][j]=min(dp[i][j],max(dp[i-1][j],sum[i]-j+p[i].b));} } for(int i=0;i<=sum[n];i++) ans=min(ans,dp[n][i]);P1026 統計單詞個數
$\texttt{solution}$難點:
雙重 \(\operatorname{DP}\) 。
字符串處理 \(+\) 難以理解的題意 。
狀態:
設 \(sum[l][r]\) 表示從 \(i\) 到 \(j\) 的單詞數 ??梢灾苯用杜e區間和單詞(字符串)轉移。
\(dp[i][k]\) 表示第 \(i\) 個位置,分了 \(k\) 塊,能得到的最多的單詞數 。
轉移:
\[dp[i][k]=\max_{p=k}^{p<i}{\{ dp[i][k] , dp[p][k-1]+sum[p+1][i] \}} \]代碼:
bool check(int l,int r) {string c=s.substr(l,r-l+1);for(int i=1;i<=t;i++) if((c.find(a[i]))==0) return true;return false; } void pre() {for(int r=1;r<=len;r++)for(int l=r;l>=1;l--){sum[l][r]=sum[l+1][r];if(check(l,r)) sum[l][r]++;} } void solve() {for(int i=1;i<=k;i++) dp[i][i]=dp[i-1][i-1]+sum[i][i];for(int i=1;i<=len;i++) dp[i][1]=sum[1][i];for(int i=1;i<=len;i++) for(int k=1;k<=min(k,i-1);k++) for(int p=k;p<i;p++)dp[i][k]=max(dp[i][k],dp[p][k-1]+sum[p+1][i]);printf("%d\n",dp[len][k]); } void init() {cin>>w>>k;string c;for(int i=1;i<=w;i++) cin>>c,s+=c;len=s.length(),s=" "+s;cin>>t;for(int i=1;i<=t;i++) cin>>a[i]; }P2679 子串
$\texttt{solution}$狀態:
設 \(dp[i][j][k][0/1]\) 表示 \(A\) 的前 \(i\) 個字符和字符串 \(B\) 的前 \(j\) 個字符用了 \(k\) 個子串,\(A\) 第 \(i\) 為取或不取的合法方案數。
初始化:對于 \(1 \le i \le n\) , \(dp[i][0][0][0] = 0\) 。
轉移:
\[dp[i][j][k][0]=(dp[i-1][j][k][0]+dp[i-1][j][k][1])\mod{1e9+7} \]\[dp[i][j][k][1]=(dp[i-1][j-1][k][1]+dp[i-1][j-1][k-1][0]+dp[i-1][j-1][k-1][1])\mod{1e9+7} \]代碼:
f[0][0][0]=1; for(int i=1;i<=n;i++) {memset(g,0,sizeof(g)),g[0][0][0]=1;for(int j=1;j<=min(i,m);j++)for(int k=1;k<=min(j,p);k++){g[j][k][0]=(f[j][k][0]+f[j][k][1])%mod;if(a[i]==b[j]) g[j][k][1]=((f[j-1][k][1]+f[j-1][k-1][0])%mod+f[j-1][k-1][1])%mod;}memcpy(f,g,sizeof(f)); } printf("%d\n",(f[m][p][0]+f[m][p][1])%mod);P1052 過河
$\texttt{solution}$難點:
離散化
\(len\) 的范圍太大,無法作為數組下標,所以先離散化,再 \(\operatorname{DP}\) 。兩點間的距離 \(d\) 大于 \(t\) 時,一定可以由 \(d\%t\) 跳過來,所以最多只需要 \(t+d\%t\) 種距離的狀態就可以表示這兩個石子之間的任意距離關系。
代碼:
bool cmp(int x,int y){ return x<y; }len=rd(); s=rd(),t=rd(),m=rd(); for(int i=1;i<=m;i++) a[i]=rd(); a[m+1]=len; sort(a+1,a+m+1,cmp); for(int i=1;i<=m+1;i++) {if(a[i]-a[i-1]>t) cnt+=(a[i]-a[i-1])%t+t;else cnt+=a[i]-a[i-1];val[cnt]=1; } memset(dp,inf,sizeof(dp)),dp[0]=0; for(int i=1;i<=cnt+t-1;i++) for(int j=s;j<=t;j++)if(i>=j) dp[i]=min(dp[i],dp[i-j]+val[i]); for(int i=cnt;i<=cnt+t-1;i++) ans=min(ans,dp[i]); printf("%d\n",ans);P5664 Emiya 家今天的飯
$\texttt{solution}$首先考慮列的限制,必然 有且只有一列 是 不合法 的:因為不可能有不同的兩列數量都 超過總數的一半 。\(ans=\) 總狀態 \(-\) 不合法狀態
計算不合法方案:
計算列的不合法方案數:每行選不超過一個的方案數 \(-\) 每行選不超過一個,且某一列選了超過一半的方案數。可以發現每一列都是獨立的,可以枚舉當某一列( 記為 \(cal\) )不合法時的方案再相加。
狀態:
先設 \(dp[i][j][k]\) 表示表示對于 \(col\) 這一列,前 \(i\) 行在 \(col\) 列中選了 \(j\) 個,在其他列中選了 \(k\) 個的非法方案數。令 \(sum[i]\) 為第 \(i\) 行的總和 。
轉移:
\[dp[i][j][k]=dp[i-1][j][k] + a[i][cal]\times dp[i-1][j-1][k]+(sum[i]-a[i][cal])\times dp[i-1][j][k-1] \]復雜度:\(O(mn^3)\) ,可以得到 \(84pts\) 。
考慮優化:
在不合法情況的轉移過程中,我們并不關心 \(j\) ,\(k\) 的具體數值,而只關心相對的大小關系。
狀態:
\(dp[i][j]\) 表示前 \(i\) 行,當前列的數比其他列的數多了 \(j\) 個 。
轉移:
\[dp[i][j]=dp[i-1][j]+a[i][cal]\times dp[i-1][j-1]+(sum[i]-a[i][cal])\times dp[i-1][j+1] \]復雜度:\(O(mn^2)\) ,復雜度在時間范圍內。
統計總方案數:
狀態:
設 \(cnt[i][j]\) 為前 \(i\) 行共選了 \(j\) 個數的方案數 。
轉移:
\[cnt[i][j]=cnt[i-1][j]+sum[i]\times cnt[i-1][j-1] \]總方案就是 \(\sum_{i=1}^n {g[n][i]}\) 。
復雜度:\(O(n^2)\) ,可以通過這道題。
代碼:
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++)a[i][j]=rd(),sum[i]=(sum[i]+a[i][j])%mod; for(int cal=1;cal<=m;cal++) {memset(dp,0,sizeof(dp)),dp[0][n]=1;for(int i=1;i<=n;i++) for(int j=n-i;j<=n+i;j++)dp[i][j]=(dp[i-1][j]+dp[i-1][j-1]*a[i][cal]%mod+dp[i-1][j+1]*((sum[i]+mod-a[i][cal])%mod)%mod)%mod;for(int j=1;j<=n;j++) unok=(unok+dp[n][j+n])%mod; } cnt[0][0]=cnt[1][0]=1; for(int i=1;i<=n;i++,cnt[i][0]=cnt[i-1][0]) for(int j=1;j<=n;j++)cnt[i][j]=(cnt[i-1][j]+sum[i]*cnt[i-1][j-1]%mod)%mod; for(int i=1;i<=n;i++) ans=(ans+cnt[n][i])%mod; printf("%lld\n",(ans+mod-unok)%mod);P2258 子矩陣
$\texttt{solution}$題意:
在 \(n\times m\) 的矩陣中選取 \(r\times c\) 的子矩陣(可以跳行 \(/\) 跳列間隔選取),使子矩陣相鄰兩元素的差之和最小 。
題解:
算法:枚舉 \(+ \operatorname{DP}\) 。
考慮到 \(n,m\) 比較小,所以先枚舉選出那些行 ,這里的復雜度為 \(O(C_n^r)\) 。
之后考慮 \(\operatorname{DP}\) 選取列 :
1. 預處理:
處理出單獨選一列,這 \(r\) 行上下之間的差之和( \(lie[i]\) ) 。
再處理如果選了第 \(i\) 列和第 \(j\) 列,這兩列橫著的 \(r\) 行元素的差值之和( \(hang[i][j]\) ) 。
2. \(\operatorname{DP}\) :
設 \(dp[i][j]\) 表示在前 \(i\) 行中選了 \(j\) 行的最小差值之和 。
轉移:
-
當 \(j=1\) 時,\(dp[i][j]\) 為僅選第 \(i\) 列的差值之和 。
-
其他情況,可以枚舉 \(k=[j-1,i-1]\) ,考慮子矩陣的第 \(j-1\) 選擇為 \(k\) 的情況下的代價是多少,并取 \(\min\) 轉移。
代碼:
int ans=inf; void solve() {for(int i=1;i<=m;i++) lie[i]=0;for(int i=1;i<m;i++) for(int j=i+1;j<=m;j++) hang[i][j]=0;for(int i=1;i<=m;i++) for(int j=2;j<=r;j++) lie[i]+=abs(a[ch[j]][i]-a[ch[j-1]][i]);for(int i=1;i<m;i++) for(int j=i+1;j<=m;j++) for(int k=1;k<=r;k++)hang[i][j]+=abs(a[ch[k]][j]-a[ch[k]][i]);for(int i=1;i<=m;i++) for(int j=1,Limit=min(i,c);j<=Limit;j++){if(j==1) dp[i][j]=lie[i];else{dp[i][j]=inf;for(int k=j-1;k<i;k++) dp[i][j]=min(dp[i][j],dp[k][j-1]+lie[i]+hang[k][i]);}}for(int i=c;i<=m;i++) ans=min(ans,dp[i][c]); } void dfs(int Left,int st) {if(!Left){solve();return;}if(st>n) return;for(int i=st;i<=n-Left+1;i++) ch[r-Left+1]=i,dfs(Left-1,i+1); }dfs(r,1); printf("%d\n",ans);P6064 [USACO05JAN]Naptime G
$\texttt{solution}$算法:線性 \(\operatorname{DP}\)
列出基礎轉移方程
先不考慮第 \(n\) 個小時與次日第 \(1\) 個小時連續 。
設 \(dp[i][j][0/1]\) 表示在第 \(i\) 個小時,已經在床上躺了 \(j\) 個小時,\(0\) 表示這個小時沒在床上,\(1\) 表示這個小時正躺在床上 。
轉移方程:( 初始值 \(dp[1][0][0]=dp[1][1][1]=0\) ,其他為 \(-\inf\) )
\[\begin{cases}dp[i][j][0]=\max{\{dp[i-1][j][0],dp[i-1][j][1]\}}\\dp[i][j][1]=\max{\{dp[i-1][j-1][0],dp[i-1][j-1][1]+u[i]\}}\end{cases} \]目標:\(\min{\{dp[n][b][0],dp[n][b][1]\}}\) 。
完善轉移方程
考慮第 \(n\) 個小時與次日第 \(1\) 個小時連續 ,即強制第第 \(n\) 個小時睡覺 。
初始值:\(dp[1][0][0]=0,dp[1][1][1]=u[1]\) ,其他為 \(-\inf\) 。
目標:\(dp[n][m][1]\) 。
最終答案為兩種情況的較大值。
代碼:
#define inf 0x7f7f7f7f #define Maxn 3835 int n,b,ans,a[Maxn],dp[Maxn][Maxn][2];// 一下代碼片段插入在 main 函數中 n=rd(),b=rd(); for(int i=1;i<=n;i++) a[i]=rd(); memset(dp,-inf,sizeof(dp)),dp[1][1][1]=dp[1][0][0]=0; for(int i=2;i<=n;i++) {dp[i][0][0]=dp[i-1][0][0];for(int j=1;j<=b;j++){dp[i][j][0]=max(dp[i-1][j][1],dp[i-1][j][0]);dp[i][j][1]=max(dp[i-1][j-1][0],dp[i-1][j-1][1]+a[i]);} } ans=max(dp[n][b][0],dp[n][b][1]); memset(dp,-inf,sizeof(dp)),dp[1][1][1]=a[1],dp[1][0][0]=0; for(int i=2;i<=n;i++) {dp[i][0][0]=dp[i-1][0][0];for(int j=1;j<=b;j++){dp[i][j][0]=max(dp[i-1][j][1],dp[i-1][j][0]);dp[i][j][1]=max(dp[i-1][j-1][0],dp[i-1][j-1][1]+a[i]);} } ans=max(ans,dp[n][b][1]); printf("%d\n",ans);P1043 數字游戲
$\texttt{solution}$算法:環形 \(\operatorname{DP}\) 。
方法:
破環成連,把環變為兩倍,統計答案的時候把答案掃一遍。
用 \(dp[l][r][p]\) 表示把 \(l\) 至 \(r\) 這一個區間分為 \(p\) 段的最小 \(/\) 最大代價 。
代碼:
int ansmin=inf,ansmax; int MAX[Maxn][Maxn][Maxm],MIN[Maxn][Maxn][Maxm]; inline int mod(int x) { return ((x%10)+10)%10; } // 保證是正數n=rd(),m=rd(); for(int i=1;i<=n;i++) a[i]=a[i+n]=rd(); for(int i=1;i<=n*2;i++) sum[i]=sum[i-1]+a[i]; memset(MIN,inf,sizeof(MIN)); // ↓ 初始化一些狀態 for(int l=1;l<=n*2;l++) for(int r=l;r<=n*2;r++) MAX[l][r][1]=MIN[l][r][1]=mod(sum[r]-sum[l-1]); for(int p=2;p<=m;p++) for(int l=1;l+p-1<=n*2;l++) for(int r=l+p-1;r<=n*2;r++) for(int k=l+p-2;k<r;k++) {MAX[l][r][p]=max(MAX[l][r][p],MAX[l][k][p-1]*mod(sum[r]-sum[k]));MIN[l][r][p]=min(MIN[l][r][p],MIN[l][k][p-1]*mod(sum[r]-sum[k])); } // k 是枚舉轉移點 for(int i=1;i<n;i++) ansmin=min(ansmin,MIN[i][i+n-1][m]),ansmax=max(ansmax,MAX[i][i+n-1][m]); // 掃一遍答案 printf("%d\n%d\n",ansmin,ansmax);P2331 [SCOI2005]最大子矩陣
$\texttt{solution}$題意:
這里有一個 \(n\times m\) 的矩陣,請你選出其中 \(k\) 個子矩陣,使得這個 \(k\) 個子矩陣分值之和最大。
注意:選出的 \(k\) 個子矩陣不能相互重疊。
其中,\(1\le n\le 100,1\le m\le 2,1\le k\le 10\)
題解:
注意到 \(m\) 比較小,分為幾類:
當 \(m=1\) 時,是普通的最大連續字段和,只不過是 \(k\) 個:
設 \(dp[i][j]\) 表示前 \(i\) 個數中取出 \(j\) 個矩形的最大和
轉移:
- 選:
- 不選:
復雜度 \(O(n^2\times k)\)
當 \(m=2\) 時,設 \(f[i][j][k]\) 表示第一列選到第 \(i\) 個數,第二列選到第 \(j\) 個數時,總共 \(k\) 個子矩形的答案
轉移有 \(4\) 種情況
- 當這一位什么都不做的時候:
- 當僅選取第一列的某段區間時:
- 當僅選取第二列的某段區間時:
- 當 \(i==j\) 時,可以選取兩列一起的
最后所有情況取 \(\max\) 。
復雜度 \(O(n^3\times k)\)
CF1174E Ehab and the Expected GCD Problem
$\texttt{solution}$首先考慮在權值最大時第一個數一定為 \(2^x2^y\) ,且 \(y\le 1\) 。
再分析往下填的數,考慮 \(dp[i][j][k]\) 表示填到第 \(i\) 個,前綴 \(\gcd\) 為 \(2^j3^k\) 時的方案數。
可以填 \(2^j3^k,2^{j-1}3^k,2^j3^{k-1}\) 的倍數,分別討論。
CF149D Coloring Brackets
$\texttt{solution}$(需要想到區間 \(\text{DP}\) )
\(dp(l,r,i,j)\) 表示區間 \([l,r]\) 中,左端點顏色為 \(i\) ,右端點顏色為 \(j\) 的涂色方案數。
分為三類情況:
- \(l+1=r\) :直接賦值。
- \(match(l)=r\) :由 \(dp(l+1,r-1,,)\) 轉移而來。
- \(match(l)!=r\) :由 \(dp(l,match(l),,)\times dp(match(l)+1,r,,)\) 轉移而來。
由于這一題的局部最優解與全局最優解之間沒有直接方便的轉移方式,所以使用遞歸的方式求出 \(\text{DP}\) 值。
P3592 [POI2015]MYJ
$\texttt{solution}$因為 \(n\le 50,m\le 300\),所以考慮一個 \(O(n^3)\) 的算法,這樣容易想到區間 \(\text{dp}\)。
把付的錢離散化
\(cnt(i,j)\) : \([l,r]\) 中,在 \(i\) 位置填顏色 \(j\) 的消費人數
\(dp(l,r,k)\) :在 \([l,r]\) 中最少的錢為 \(k\) 時的最大獲得錢數
在轉移時與 \(dp(l,r,k+1)\) 取 \(\max\) ,因為 \(k\) 比 \(k+1\) 少
記下這個狀態最優時,【最少的錢的位置】與【最少的錢的錢的多少】
CF840C On the Bench
$\texttt{solution}$把 \(p\) 除去所有平方因子,轉化為相鄰的 \(p\) 互不相同。
想象把數一個一個塞到原序列中。
\(dp(i,j,k)\) 放了 \(i\) 個,\(j\) 個相同且相鄰,\(k\) 個與第 \(i\) 個數相同且相鄰
為什么想到要這么假設呢?
為了讓數字相同的一起處理,把 \(p\) 排好序,并且在顏色變化時記得更新~
起始狀態 \(dp(0,0,0)\)
目標 \(dp(n,0,0)\)
處理出一個數和之前多少個數相同 ( \(pre\) )
若塞入后和左/右其一相同:
\[dp[i-1][j][k]\times(pre+pre-k[可以塞的位置])->dp[i][j+1][k+1] \]若塞入后與左右都不同,但左右相同:
\[dp[i-1][j][k]\times(j-k[可以塞的位置])->dp[i][j-1][k] \]若塞入后與左右都不同,且左右不同:
\[p[i-1][j][k]\times(i[總位置數]-(pre+pre-k)[情況 1 ]-(j-pre)[情況 2 ])->dp[i][j][k] \]CF830D Singer House
$\texttt{solution}$考慮把深度一個一個累加,去考慮怎樣從上一個階段轉移到這一個階段。
假設增加了一層,不妨假設用一個新的根節點合并兩顆深度為 \(n-1\) 的子樹(明顯這樣更好維護呀)
設此時的答案為 \(f_n\) 。
一個思路是考慮這條路徑是否經過根節點,那么有幾種情況:
這條路徑只包含根節點;這條路徑從下面某棵子樹內一條路徑連上來,再連接下去
乍一看似乎能做,但是我們會發現,從一棵子樹中連上來的路徑可能會連回同一棵子樹,那么如果我們要算\(f_n\) ,就必須算出從深度為 \(n-1\) 的子樹內選擇兩條不相交的路徑的方案數 \(g_{n-1}\) 。
你可能會想繼續討論 \(g_n\) 的方案數,但是你會發現,你要算 \(g_n\) ,還得算深度為 \(n-1\) 的樹種選三條不相交路徑的方案數……
既然如此,我們觀察一下數據范圍, 不妨多設一維狀態:
令 \(f_{n,k}\) 代表在深度為 \(n\) 的樹中選擇 \(k\) 條不相交路徑的方案數。
看上去似乎變難了,畢竟原題只讓我們求一條路徑的方案數。
但是我們發現,這個“加強”版本似乎更好做了,因為轉移變得十分簡單:
\[f_{n,k}=\sum_{i+j=k-1}f_{n-1,i}\times f_{n-1,j} \]\[+\sum_{i+j=k}f_{n-1,i}\times f_{n-1,j} \]\[+\sum_{i+j=k}f_{n-1,i}\times f_{n-1,j}\times (2k) \]\[+\sum_{i+j=k+1}f_{n-1,i}\times f_{n-1,j}\times (k+1)\times k \]這四種情況分別是:根節點單獨形成一條鏈、根節點不屬于任何一條鏈、根節點與左右子樹內某條鏈連在一起(分從鏈的尾端連上來和連到鏈的開頭兩種情況)、還有根節點從某條鏈上連上再連到另一條鏈上去。
參考
總結
以上是生活随笔為你收集整理的【做题记录】DP 杂题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【做题记录】 [JLOI2011]不等式
- 下一篇: 华为鸿蒙 HarmonyOS 4 升级设