【算法学习】区间动态规划

区间型动态规划

区间DP做[l, r]时候, 区间外都是不进行操作的。
大板子

    //初始化
    memset(dp, 0x3f, sizeof dp);
	for ( int i = 1; i <= n; ++ i )dp[i][i] = 1;
	
	for ( int len = 1; len <= n; ++ len ) {
		for ( int l = 1; l + len - 1 <= n; ++ l ) {
			int r = l + len - 1;
			for ( int k = l; k <= r - 1; ++ k ) {  //合并+合并产生的权值
				dp[l][r] = min(dp[l][r], dp[l][k] + dp[k + 1][r] + val);
				//额外条件更新
			}
		}
	}
	cout << dp[1][n] << endl;

阶段(长度),状态(左右端点),决策,按由外到内顺序构成三层循环。
状态方程:dp[i][j] ,从i到j一段区间的性质。
状态转移顺序:一个区间由被他包含且比他更小的区间转移过来,(由此,我们写一些题的时候可以只研究左右端点)。

基础题

题1:石子合并
环形序列序列复制接在后面。
dp[]
题2:涂色
image

  • 确定状态:
    最后一步:将最后一块涂色
    子问题:子区间的最小涂色数目
  • 状态方程:dp[i][j] 从i到j一段区间的最小涂色数目
  • 转移顺序:从小区间到大区间
    转移方式:
    我们研究i,j两个区间端点的性质(因为大区间的边界是从小区间扩展来的)
    1. i和j的颜色相等:
      这时就可以看作在涂区间[i+1,j]时候向左多涂了一格,或在涂区间[i,j-1]时候向右多涂了一格
      dp[i][j]=min(dp[i+1][j],dp[i][j-1]);
    2. i和j的颜色不相等:
      这时就要左右区间分别涂色,枚举分界点
      f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]);
  • 边界情况:
    只有一块时候,dp[i][i]=1;
string ss;
int f[55][55];
int main()
{
    cin>>ss;
    int len=ss.length();
    memset(f,0x3f,sizeof f);
    for(int i=0;i<len;i++)
    f[i][i]=1;
    int j;
    for(int d=1;d<len;d++)
    for(int i=0;i+d<len;i++)
    {
        j=i+d;
        if(ss[i]==ss[j])
        f[i][j]=min(f[i+1][j],f[i][j-1]);
        else
        {
            for(int k=i;k<j;k++)
            {
                f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]);
            }
        }
    }
    cout<<f[0][len-1]<<endl;
    return 0;
}

题3:
POJ3280
题目大意:给出每个字母增添和删除的成本,求使原序列变成回文串最小成本

  • 确定状态:
    dp[i][j] 表示区间[i,j]变为回文串的最小花费
  • 转移顺序:
    从小区间到大区间
    转移方式:
    还是看两端i,j
    1. 若i,j相同,则不需要花费,问题转化为求子区间[i+1,j-1]
      dp[i][j]=dp[i+1][j-1];
      2.若i,j不相同,则需要两端操作成本,问题转化为添加或删去左端点或右端点
      (对i操作:删除左边的i或在右边增添i,子问题的状态都是一样的 ,所以w只需要记录增删中的最小值)
      dp[i][j]=min(dp[i+1][j]+w[ss[i]-'a'],dp[i][j-1]+w[ss[j]-'a']);
const int maxn=2005;
int dp[maxn][maxn];
int w[26];char ss[maxn];
int main(){
    int n,m,x,y;char s;
    cin>>n>>m;
    for(int i=1;i<=m;i++)cin>>ss[i];
    while(n--){
        cin>>s>>x>>y;
        w[s-'a']=min(x,y);
    }
    for(int len=1;len<=m;len++){
        for(int i=1;i+len<=m;i++){
            int j=i+len;
            dp[i][j]=0x3f3f3f3f;
            if(ss[i]==ss[j])dp[i][j]=dp[i+1][j-1];
            else{
                dp[i][j]=min(dp[i+1][j]+w[ss[i]-'a'],dp[i][j-1]+w[ss[j]-'a']);
            }
        }
    }
    cout<<dp[1][m]<<endl;
    return 0;
}

题4:
括号匹配
题目大意:给出括号序列,求最大匹配
样例:
((())) 6
()()() 6
([]]) 4
)[)( 0
([][][) 6

  • 确定状态:dp[i][j],区间[i,j]的最大括号匹配数
  • 转移方式:
    1.s[i]==s[j]时,直接转移,f[i][j]=f[i+1][j-1]+2;
    2.s[i]!=s[j]时,枚举分割点f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]);
    注意:s[i]= =s[j]时,仍要枚举分割点,
    例如:()()() dp[1][6]=dp[2][5]+2=4 但是:dp[0][1]+dp[2][5]=6
int f[105][105];
string ss;
bool check(int l,int r)
{
    if((ss[l]=='('&&ss[r]==')')||(ss[l]=='['&&ss[r]==']'))
        return 1;
    else return 0;
}
int main()
{
    while(cin>>ss)
    {
        int len=ss.length();
        if(ss=="end")
            break;
        memset(f,0,sizeof f);
        for(int d=1;d<len;d++)
            for(int i=0;i+d<len;i++)
            {
                int j=i+d;
                if(check(i,j))
                    f[i][j]=f[i+1][j-1]+2;
                for(int k=i;k<j;k++)
                {
                    f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]);
                }
            }
        cout<<f[0][len-1]<<endl;
    }
    return 0;
}

进阶题

题5:关路灯

状态方程:
我们发现一般的用dp[i][j]未关闭灯的总功率表示是不够用的,因为老头在区间中的位置影响下一阶段的操作,所以用dp[i][j][0]表示区间[i,j]全灭后,老头站在i位置,用dp[i][j][1]表示区间[i,j]全灭后,老头站在j位置
转移方式:
老头最终站在i点时
关闭一个区间的所有路灯,可能是从i+1到i直接走到,也可能是从i+1走到j,再返回到i,

                                 
	f[i][j][0]=min( f[i+1][j][0]+(a[i+1]-a[i])*(sum[i]+sum[n]-sum[j])
               f[i+1][j][1]+(a[j]-a[i])*(sum[i]+sum[n]-sum[j]) );
int a[55],b[55],s[55];int dp[55][55][2];
int main(){
    int n,c;
    cin>>n>>c;
    for(int i=1;i<=n;i++){
        cin>>a[i]>>b[i];
        s[i]+=s[i-1]+b[i];
    }
    memset(dp,0x3f,sizeof dp);
    dp[c][c][1]=dp[c][c][0]=0;
    for(int len=1;len<=n;len++){
        for(int i=1;i+len<=n;i++){
            int j=i+len;
            //注意i也一直耗电
            dp[i][j][0]=min(dp[i+1][j][0]+(a[i+1]-a[i])*(s[n]-(s[j]-s[i]))
                    ,dp[i+1][j][1]+(a[j]-a[i])*(s[n]-(s[j]-s[i])));
            //注意j也一直耗电
            dp[i][j][1]=min(dp[i][j-1][1]+(a[j]-a[j-1])*(s[n]-(s[j-1]-s[i-1]))
                    ,dp[i][j-1][0]+(a[j]-a[i])*(s[n]-(s[j-1]-s[i-1])));
        }
    }
    int ans=min(dp[1][n][0],dp[1][n][1]);
    cout<<ans<<endl;
    return 0;
}

题6:HDU3506
基础+四边不等式优化
题7POJ1651
矩阵连乘

总结

posted @ 2021-07-24 22:09  qingyanng  阅读(107)  评论(1编辑  收藏  举报