1. 程式人生 > 其它 >淺談區間DP相關例題

淺談區間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>
using
namespace 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大多數和區間有關係,且是可以從比自己小區間轉移過來的,由小問題解決大問題。(水總結)