淺談區間DP相關例題
前言:
大家都知道博主的DP非常的不好,那麼最近在刷一些區間DP。我大概做了有個15道左右。我在中間做了一些篩選,那麼現在來總結一下。有些博主認為有價值或有些難度的題,博主才會放出程式碼,其餘的只會做出簡要說明。
例題:
1.洛谷1005
我們發現每一行是獨立的,那麼就只用考慮每一行的情況
dp[l][r]很明顯是根據dp[l-1][r]和dp[l][r-1]轉過來的,那就是個裸的區間dp啊。那就對這道題說拜拜吧。
2.4302字串摺疊
嗯這個中間可以巢狀,所以就直接區間DP啦,然後就是整體判斷能不能摺疊。
我程式碼給的是它的一個子母題,也就是輸出路徑,但精髓是一樣的。
#include<bits/stdc++.h> usingnamespace std; string s; string q[101][101]; int n,m[101],f[110][110]; bool check(int l,int r,int len) { for(int i=l;i<=r;i++) { if(s[i]!=s[(i-l)%len+l]) return 0; } return 1; } string num[110]; int main() { for(int i=1;i<=9;i++) m[i]=1,num[i]=char(i+48); for(int i=10;i<=99;i++) m[i]=2,num[i]+=char(i/10+48),num[i]+=char(i%10+48); m[100]=3; num[100]="100"; while(cin>>s) { n=s.size(); s=' '+s; memset(f,0x3f,sizeof(f)); for(int i=1;i<=n;i++) f[i][i]=1; for(int i=1;i<=n;i++) q[i][i]=s[i]; for(int l=2;l<=n;l++) for(int i=1,j=i+l-1;j<=n;i++,j++) { f[i][j]=l; q[i][j]=s.substr(i,l); for(int k=i;k<j;k++) { if(f[i][j]>f[i][k]+f[k+1][j]) { f[i][j]=f[i][k]+f[k+1][j]; q[i][j]=q[i][k]+q[k+1][j]; } if(l%(k-i+1)!=0) continue ; if(check(i,j,(k-i+1))) { if(f[i][j]>f[i][k]+2+m[l/(k-i+1)]) { f[i][j]=f[i][k]+2+m[l/(k-i+1)]; q[i][j]=num[l/(k-i+1)]+'('+q[i][k]+')'; } } } } cout<<q[1][n]<<endl; } return 0; }
3.壓縮
我一開始是想的巢狀,但是我們可以發現因為這個RM的匹配問題,所以這一道題無法巢狀,那麼怎麼辦呢?那就只能判斷中間是否有M了。那麼我們設計DP狀態dp[l][r][0/1]就是一個區間是否有M,而且我們預設一段區間的最左端有一個M,但這不算在我們的長度中。
然後每一次還要判斷是否能折半摺疊。程式碼和上題差不多。
這個題的轉移方程不太好想,應該多體會體會。
4.3205合唱隊
dp[l][r][0/1]代表一段區間最後入隊的是左邊還是右邊,然後就是判斷它的前一位是(l+1)還是(r)或(r-1)或(l)。
5.關路燈
這個就是dp[l][r][0/1]代表一段區間關完後,老張在最左邊還是最右邊。
6.Polygon
這個先是破環成鏈。這個題不止要維護最大值,還要維護最小值,因為乘法也是可以負負得正得,其他的就沒什麼了。
7.Sue的小球
這個就和關路燈差不多,但因為自己在收集綵球時,價值也在變小,所以我們乾脆設dp[l][r][0/1]為一段區間都取完後停留在左/右的損耗的最小值。這裡求損耗的時候可以提前預處理一個字首和,最後答案就是(所有y的和-min(dp[1][n][0],dp[1][n][1]))/1000.0了,下面看程式碼:
#include <bits/stdc++.h> #define MAX (1000 + 7) using namespace std; struct Node{ int x, y, v; int operator <(Node b) const { return x < b.x; } } a[MAX]; int N, x0, ans, f0[MAX][MAX], f1[MAX][MAX]; int main() { scanf("%d%d", &N, &x0), a[N+1].x = x0; memset(f0, 0x3f, sizeof f0); memset(f1, 0x3f, sizeof f1); for (int i = 1; i <= N; i++) scanf("%d", &a[i].x); for (int i = 1; i <= N; i++) scanf("%d", &a[i].y), ans += a[i].y; for (int i = 1; i <= N; i++) scanf("%d", &a[i].v); N++; sort(a + 1, a + N + 1); for (int i = 1; i <= N; i++) if (a[i].x == x0) { f0[i][i] = f1[i][i] = 0; break; } for (int i = 1; i <= N; i++) a[i].v += a[i-1].v; for (int i = 1; i <= N; i++) for (int L = 1, R = L + i; R <= N; L++, R++) { f0[L][R] = min(f0[L+1][R] + (a[L+1].x-a[L].x) * (a[L].v+a[N].v-a[R].v), f1[L+1][R] + (a[R].x - a[L].x) * (a[L].v+a[N].v-a[R].v)); f1[L][R] = min(f0[L][R-1] + (a[R].x - a[L].x) * (a[L-1].v+a[N].v-a[R-1].v), f1[L][R-1] + (a[R].x-a[R-1].x) * (a[L-1].v+a[N].v-a[R-1].v)); } printf("%.3lf\n", (ans - min(f0[1][N], f1[1][N])) / 1000.0); }
8.1043數字遊戲
先破鏈成環,dp[l][r][k]代表一段區間被分成k組,下面放關鍵程式碼:
for(int i=1;i<=2*n;i++) { for(int j=i;j<=2*n;j++) fb[i][j][1]=fa[i][j][1]=modd(sum[j]-sum[i-1]); }
初始化
for(int k=2;k<=m;k++) for(int len=k;len<=n;len++) { for(int l=1,r=l+len-1;r<=2*n;l++,r++) { for(int j=l+k-2;j<r;j++) { fa[l][r][k]=max(fa[l][j][k-1]*fa[j+1][r][1],fa[l][r][k]); fb[l][r][k]=min(fb[l][j][k-1]*fb[j+1][r][1],fb[l][r][k]); } } }
因為初始化已經mod10了,所以根據題意中途不能mod10.
9.UVA10559 方塊消除 Blocks
這個題第一次是在黑書上看到,思路真的絕了。
我們發現因為它是乘方的消去,所以之前的那種合併就無法正確了,於是我們加了一維dp[l][r][k]代表一段區間(l,r)且r右邊有k個與r同顏色的方塊所能消去的最大值。
程式碼:
#include<bits/stdc++.h> #define lst long long #define ldb double #define N 250 using namespace std; const int Inf=1e9; int read() { int s=0,m=0;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')m=1;ch=getchar();} while(ch>='0'&&ch<='9')s=(s<<3)+(s<<1)+(ch^48),ch=getchar(); return m?-s:s; } int n; int col[N],nxt[N],hd[N]; lst dp[N][N][N]; lst Dfs(int i,int j,int k) { if(i>j)return 0; if(dp[i][j][k])return dp[i][j][k]; dp[i][j][k]=max(dp[i][j][k],Dfs(i,j-1,0)+(k+1)*(k+1)); for(int p=nxt[j];p>=i;p=nxt[p]) dp[i][j][k]=max(dp[i][j][k],Dfs(i,p,k+1)+Dfs(p+1,j-1,0)); return dp[i][j][k]; } int main() { int T=read(); for(int tt=1;tt<=T;++tt) { n=read(); memset(hd,0,sizeof(hd)); memset(dp,0,sizeof(dp)); memset(nxt,0,sizeof(nxt)); for(int i=1;i<=n;++i) { col[i]=read(); nxt[i]=hd[col[i]]; hd[col[i]]=i; } printf("Case %d: %lld\n",tt,Dfs(1,n,0)); } return 0; }
這個題需要在紙上多畫幾遍,真的很有思維難度。
總結:
區間DP大多數和區間有關係,且是可以從比自己小區間轉移過來的,由小問題解決大問題。(水總結)