区间 dp 总结

区间 \(DP\)

定义:

区间类动态规划是线性动态规划的拓展,它在分阶段地划分问题时,与阶段中元素出现的顺序和由前一阶段的哪些元素合并有很大关系。

操作:

定义 \(f[i][j]\) 表示将下标位置 \(i\)\(j\) 的所有元素合并能获得的价值的最大值。

\[f[i][j]=max(f[i][k]+f[k+1][j]+cost) \]

特点:

合并: 两个或多个部分进行整合,当然可以反过来。

特征: 能将问题分解为两两合并的形式

求解: 对整个问题设置最优解,枚举合并点,将问题分解为左右两部分,最后合并两个部分的最优值,得到原问题的最优值。

例题:

P4302 [SCOI2003]字符串折叠

懒狗题意:

字符串能不能折叠更短(原来太长了)

分析:

看字符串长度,不超过 \(100\) ,而且包括区间的折叠,也就是合并,那么用区间 \(dp\) 岂不是美哉?

我们定义 \(dp[i][j]\) 表示 \([i,j]\) 范围内,字符串能折叠成的最小形式。

当进行合并时,开始有 \(dp\) 方程:

dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);

下方对 \([i,j]\) 范围进行判断:

枚举能重叠的长度,如果 不能整除区间总长度 \((l\%len!=0)\) ,或者 区间并不是能合并的 ,就跳过。

若成功,则有转移公式:

dp[i][j]=min(dp[i][j],dp[i][k]+2+m[l/len]);

其中,\(m\) 数组为 \(l/len\) 的值。

代码:

// P4302 [SCOI2003]字符串折叠
#include<bits/stdc++.h>
using namespace std;
const int N=105;
char s[N];
int n,m[N],dp[N][N];
bool check(char s[],int n,int len){
    for(int i=len;i<n;i++)
        if(s[i]!=s[i%len])return false;
    return true;
}
int main(){
    scanf("%s",s); n=strlen(s);
    for(int i=1;i<=9;i++) m[i]=1; for(int i=10;i<=99;i++) m[i]=2; m[100]=3;
    memset(dp,0x3f3f3f3f,sizeof(dp));
    for(int i=0;i<n;i++) dp[i][i]=1;
    for(int l=2;l<=n;l++){
        for(int i=0,j=i+l-1;j<n;i++,j++){
            for(int k=i;k<j;k++) 
                dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);
            for(int k=i;k<j;k++){
                int len=k-i+1;
                if(l%len!=0) continue;
                if(check(s+i,l,len))//枚举长度
                    dp[i][j]=min(dp[i][j],dp[i][k]+2+m[l/len]);
            }
        }
    }
    printf("%d",dp[0][n-1]);
    system("pause");
    return 0;
}

P2470 [SCOI2007]压缩

题意:

算是上一题加强版,不过就是多了几个转移方程

分析:

\(dp[i][j][0]\) 表示表示 \([i-j]\) 的区间内没有 \(M\) 的情况

\(dp[i][j][1]\) 则表示i到j的区间内有M的情况

先看 \(dp[i][j][0]\),如果前半段与后半段相等,那么则有转移:

\[dp[i][j][0]=min(dp[i][j][0],dp[l][mid][0]+1) \]

其中,\(mid\) 为区间中点。

然后像平常的区间 \(dp\) 一样,枚举中间点 \(k\)

\[dp[i][j][0]=min(dp[i][j][0],dp[i][k][0]+j−k) \]

再考虑 \(dp[i][j][1]\) 的情况,此时的k枚举的是M的位置

\[dp[l][r][1]=min(dp[l][r][1],min(dp[l][k][0],dp[l][k][1])+min(dp[k+1][r][0],dp[k+1][r][1])+1) \]

最后输出 \(max(dp[1][n][0],dp[1][n][1])\) 即可。

#include<bits/stdc++.h>
using namespace std;
#define int long long 
const int N=55;

char s[N];
int n;
int dp[N][N][3];
int check(int l,int r){
    if((r-l+1)&1) return 0;
    int mid=(l+r)>>1;
    for(int i=l;i<=mid;i++) if(s[i]!=s[i+mid-l+1]) return 0;
    return 1;
}

signed main(){
    scanf("%s",s+1); n=strlen(s+1);
    for(int i=1;i<=n;i++) for(int j=i;j<=n;j++) dp[i][j][0]=dp[i][j][1]=(j-i+1);
    for(int len=2;len<=n;len++)
        for(int l=1;l+len-1<=n;l++){
            int r=l+len-1;
            if(check(l,r)) dp[l][r][0]=min(dp[l][r][0],dp[l][(l+r)/2][0]+1);
            for(int k=l;k<r;k++)
                dp[l][r][0]=min(dp[l][r][0],dp[l][k][0]+r-k);
            for(int k=l;k<r;k++)
                dp[l][r][1]=min(dp[l][r][1],min(dp[l][k][0],dp[l][k][1])+min(dp[k+1][r][0],dp[k+1][r][1])+1);
        }
    printf("%d\n",min(dp[1][n][1],dp[1][n][0]));
    system("pause");
    return 0;
}
posted @ 2021-09-26 21:55  Evitagen  阅读(65)  评论(0编辑  收藏  举报