区间dp题

CSU1592 石子归并

题意:

现在有n堆石子,第i堆有ai个石子。现在要把这些石子合并成一堆,每次只能合并相邻两个,每次合并的代价是两堆石子的总石子数。求合并所有石子的最小代价。
(n<=100)

思路:

1.从小到大枚举区间长度
2.枚举区间起点(起点+长度可求出终点)
3.枚举区间分割点
4.取min

code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=105;
const int inf=0x3f3f3f3f;
int d[maxm][maxm];//d[i][j]表示合并区间[i,j]的最小代价
int sum[maxm];
int a[maxm];
int n;
signed main(){
    int T;
    cin>>T;
    while(T--){
        memset(d,inf,sizeof d);
        cin>>n;
        for(int i=1;i<=n;i++){
            cin>>a[i];
            d[i][i]=0;
        }
        for(int i=1;i<=n;i++){//前缀和,用于快速计算区间和
            sum[i]=sum[i-1]+a[i];
        }
        for(int len=2;len<=n;len++){//枚举区间长度
            for(int i=1;i<=n;i++){//枚举起点
                int j=i+len-1;//终点
                if(j>n)break;
                for(int k=i;k<=j-1;k++){//枚举分割点k,合并d[i][k]和d[k+1][j];
                    d[i][j]=min(d[i][j],d[i][k]+d[k+1][j]+sum[j]-sum[i-1]);
                }
            }
        }
        cout<<d[1][n]<<endl;
    }
    return 0;
}

hdu 3506 Monkey Party(环形石子归并+四边形不等式优化)

题意:

现在有n堆石子围成一圈,第i堆有ai个石子。现在要把这些石子合并成一堆,每次只能合并相邻两个,每次合并的代价是两堆石子的总石子数。求合并所有石子的最小代价。
(n<=1000)

思路:

环形石子归并
把n复制一遍扩展到2n即可
但是n很大,O(n3)已经不能满足要求了,所以需要利用四边形不等式优化

code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=2e3+5;
int d[maxm][maxm];//d[i][j]表示合并区间[i,j]的最小代价
int s[maxm][maxm];//s[i][j]表示合并区间[i,j]的最优分割点k
int sum[maxm];
int a[maxm];
int n;
signed main(){
    int n;
    while(cin>>n){
        for(int i=1;i<=n+n;i++){//初始化为inf
            for(int j=i+1;j<=n+n;j++){
                d[i][j]=1e9;
            }
        }
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
            a[i+n]=a[i];
        }
        for(int i=1;i<=n+n;i++){
            d[i][i]=0;
            s[i][i]=i;
            sum[i]=sum[i-1]+a[i];
        }
        for(int len=2;len<=n;len++){
            for(int i=1;i<=n+n;i++){
                int j=i+len-1;
                if(j>n+n)break;
                for(int k=s[i][j-1];k<=s[i+1][j];k++){
                    int temp=d[i][k]+d[k+1][j]+sum[j]-sum[i-1];
                    if(d[i][j]>temp){
                        d[i][j]=temp;
                        s[i][j]=k;
                    }
                }
            }
        }
        int ans=1e9;
        for(int i=1;i<=n;i++){
            ans=min(ans,d[i][i+n-1]);
        }
        printf("%d\n",ans);
    }
    return 0;
}

poj3186 Treats for the Cows

题意:

给n个数字,每次可以取出最左边或者最右边的数字,假设第i次取的数字是x,则可以获得i*x的收益,问取完所有数的最大收益。

思路:

令d(i,j)表示取完区间[i,j]的最大收益

每一次都只有取左边和取右边两种:
1.取左边:d(i+1,j)+value1
2.取右边:d(i,j-1)+value2
取max就行了

从小区间到大区间递推即可

code:
#include<cstdio>
#include<iostream>
using namespace std;
const int maxm=2e3+5;
int d[maxm][maxm];//d[i][j]表示区间[i,j]全部取出的最大收益
int a[maxm];
signed main(){
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        d[i][i]=a[i]*n;//只有一个的时候,最大值为最后一天卖出去的价格a[i]*n
    }
    for(int len=2;len<=n;len++){
        for(int i=1;i<=n;i++){
            int j=i+len-1;
            if(j>n)break;
            d[i][j]=max(d[i+1][j]+(n-len+1)*a[i],d[i][j-1]+(n-len+1)*a[j]);
        }
    }
    cout<<d[1][n]<<endl;
    return 0;
}

poj2955 Brackets(括号匹配)

题意:

给一个括号字符串
问最多匹配多少组括号

思路:

合并两个小区间时:
1.强行合并,直接把两个小区间的匹配数相加
2.合并之前判断大区间两端点能否匹配,但是如果两端点可以匹配不一定就比强行合并的更优,
例如:序列1:"()",序列2:"()"
序列1的左括号和序列2的右括号可以匹配,这时候总匹配数为“)”和“(”的匹配数+1,
显然不比序列1和序列2直接合并的匹配数2更优,不过某些情况下还是能更优的,例如"((“和”])"
因此考虑第二种情况的时候第一种情况也要计算(刚开始我没考虑全)

code:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxm=105;
char s[maxm];
int d[maxm][maxm];
bool check(int i,int j){//判断是否是一对括号
    if(s[i]=='['&&s[j]==']')return 1;
    if(s[i]=='('&&s[j]==')')return 1;
    return 0;
}
signed main(){
    while(scanf("%s",s+1)!=EOF){
        if(s[1]=='e')break;
        int n=strlen(s+1);
        memset(d,0,sizeof d);
        for(int len=2;len<=n;len++){
            for(int i=1;i<=n;i++){
                int j=i+len-1;
                if(j>n)break;
                if(check(i,j)){//如果两端匹配
                    d[i][j]=d[i+1][j-1]+1;
                }
                for(int k=i;k<j;k++){//强行合并两个区间
                    d[i][j]=max(d[i][j],d[i][k]+d[k+1][j]);
                }
            }
        }
        cout<<d[1][n]*2<<endl;
    }
    return 0;
}

poj3280 Cheapest Palindrome(修改为回文串的最小代价)

题意:

给定一个字符串S,字符串S的长度为M(M≤2000),字符串S所含有的字符的种类的数量为N(N≤26),然后给定这N种字符Add与Delete的代价,求将S变为回文串的最小代价和。

思路:

设d(i,j)为区间[i,j]变成回文串的最小代价
1.d(i,j)从d(i+1,j)转移来,这时候可以删除左边的s[i],或右边添加一个s[i],因此花费为add和del的最小值
2.d(i,j)从d(i,j-1)转移来,这时候可以删除右边的s[j],或左边添加一个s[j],因此花费为add和del的最小值

可以发现:一个字符修改的代价就是add和del的最小值,因此只需要记录两者的最小值

code:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const int maxm=2e3+5;
char s[maxm];
int d[maxm][maxm];
int cost[26];
signed main(){
    int n,m;
    scanf("%d%d",&n,&m);//n种字符,字符串长度为m
    scanf("%s",s+1);
    for(int i=1;i<=n;i++){
        char t[2];
        scanf("%s",t);
        int v=t[0]-'a';
        int x,y;
        scanf("%d%d",&x,&y);
        cost[v]=min(x,y);
    }
    for(int i=1;i<=m;i++){
        for(int j=i+1;j<=m;j++){
            d[i][j]=1e9;
        }
    }
    for(int len=2;len<=m;len++){
        for(int i=1;i<=m;i++){
            int j=i+len-1;
            if(j>m)break;
            if(s[i]==s[j]){
                d[i][j]=d[i+1][j-1];
            }else{
                d[i][j]=min(d[i][j],d[i+1][j]+cost[s[i]-'a']);//从[i+1,j]转移
                d[i][j]=min(d[i][j],d[i][j-1]+cost[s[j]-'a']);//从[i,j-1]转移
            }
        }
    }
    printf("%d\n",d[1][m]);
    return 0;
}

LightOJ1422 Halloween Costumes

题意:

小灰灰参加圣诞节的一些派对,并且需要穿上对应派对的衣服,所以他需要多次换衣服,为了方便,他可以选择脱掉一些衣服或者穿上新衣服,比如说,他穿着超人的衣服,外面又穿着死侍的衣服,当他要参加超人服装派对时,他可以选择脱掉死侍的衣服(因为死侍衣服的里面有超人的衣服),或者他可以在穿一件超人的衣服,小灰灰是个爱干净的人,当他脱下死侍的衣服后,如果需要再穿死侍的衣服,他会选择再穿一件新的。(如果他先穿A衣服,又穿上B衣服,再穿一件C衣服,如果他想让最外面的衣服是A,他可以选择直接穿一件A,或者先把C脱掉,再把B脱掉)。
输入n,然后给n个派对需要的衣服(从左到右,顺序不能改变)

思路:

d(i,j)表示[i,j]需要的最少衣服数量
最差的结果是每天都换一件新的,即d(i,j)=j-i+1
思考如何才能少掉几件
发现当a[i]==a[j]的时候,第i天穿的不脱下来,第j天就能用上,这个情况d(i,j)=d(i,j-1)
否则枚举分割点强行合并区间取min

code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=205;
int d[maxm][maxm];
int a[maxm];
signed main(){
    int T;
    cin>>T;
    int cas=1;
    while(T--){
        int n;
        cin>>n;
        for(int i=1;i<=n;i++){
            cin>>a[i];
        }
        for(int i=1;i<=n;i++){//初始化为每天都换一件新的
            for(int j=i;j<=n;j++){
                d[i][j]=j-i+1;
            }
        }
        for(int len=2;len<=n;len++){
            for(int i=1;i<=n;i++){
                int j=i+len-1;
                if(j>n)break;
                if(a[i]==a[j]){//第i天和第j天相同
                    d[i][j]=d[i][j-1];
                }else{
                    for(int k=i;k<j;k++){
                        d[i][j]=min(d[i][j],d[i][k]+d[k+1][j]);
                    }
                }
            }
        }
        printf("Case %d: ",cas++);
        cout<<d[1][n]<<endl;
    }
    return 0;
}

hdu2476 String painter

题意:

有两个长度相等,都只由小写字母构成的字符串A和B.。有一把刷子,可以把字符串中连续的一段区间刷成一个相同的字符,例如,对于字符串"vandarkholme",把区间[3,6]刷成’d’,可以变成"vanddddholme"。把字符串A变成B最少要刷多少次?

思路:

似乎是bzoj1260的加难版

先区间dp计算出空串刷成B的次数
然后再计算A刷成B的次数

code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=105;
int d[maxm][maxm];
char s[maxm];
char t[maxm];
int ans[maxm];
signed main(){
    while(scanf("%s%s",s+1,t+1)!=EOF){
        int n=strlen(s+1);
        //计算空字符串转化为T
        for(int i=1;i<=n;i++){
            d[i][i]=1;
        }
        for(int len=2;len<=n;len++)
            for(int i=1;i<=n;i++){
                int j=i+len-1;
                if(j>n)break;
                d[i][j]=d[i+1][j]+(t[i]!=t[j]);//如果t[i]==t[j],则一次就能一起刷完 
                for(int k=i+1;k<j;k++){
                    d[i][j]=min(d[i][j],d[i+1][k]+d[k+1][j]+(t[i]!=t[k]));
                }
            }
        }
        //计算S转化为T
        ans[1]=(s[1]!=t[1]);
        for(int i=2;i<=n;i++){
            ans[i]=d[1][i];
            if(s[i]==t[i]){
                ans[i]=ans[i-1];
            }else{
                for(int j=1;j<i;j++){
                    ans[i]=min(ans[i],ans[j]+d[j+1][i]);
                }
            }
        }
        cout<<ans[n]<<endl;
    }
    return 0;
}

Acwing284 金字塔

题意:

给一个字符串,问该字符串是多少种形态的树的dfs序,答案取模1e9

思路:

n个点的树一共有(n-1)条边,每条边下去的时候输出一个字母,回来的时候输出一个字母,加上最开始的根节点字母
那么n个点的树的dfs序一定是长度为2(n-1)+1即(2n-1)的字符串。

1.如果给定字符串是偶数长度,则一定无解。
2.d(i,j)表示区间[i,j]字符串作为dfs序的树的形态数量
枚举分割点k,表示s(i)进去s(k)出来,s(k)进去s(j)出来,(k+1)-(j-1)是最后一颗子树的序列,
(i+1)-(k-1)是除了最后一颗子树的序列(可以为空),i,k,j都是树根(需要满足s(i)=s(k)=s(j))。
因为满足乘法原理,所以方案数d(i,j)+=d(i,k)*d(k+1,j-1),遍历完所有k即可计算出区间内的所有方案

code:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<map>
using namespace std;
#define int long long
const int maxm=305;
const int mod=1e9;
char s[maxm];
int d[maxm][maxm];
signed main(){
    scanf("%s",s+1);
    int n=strlen(s+1);
    if(n%2==0){//dfs序一定是2n+1个字符
        cout<<0<<endl;
    }else{
        for(int i=1;i<=n;i++){//init
            d[i][i]=1;
        }
        for(int len=2;len<=n;len++){
            for(int i=1;i<=n;i++){
                int j=i+len-1;
                if(j>n)break;
                if(s[i]==s[j]){//首尾相同才是dfs序
                    for(int k=i;k<j;k+=2){
                        if(s[k]==s[j]){//s[i]进去s[k]回来,s[k]进去s[j]回来,最后一棵子树区间为[k+1,j-1]
                            d[i][j]=(d[i][j]+d[i][k]*d[k+1][j-1])%mod;
                        }
                    }
                }
            }
        }
        cout<<d[1][n]<<endl;
    }
    return 0;
}

CodeForces607 B. Zuma

题意:

给一个长度为n的数组,每次操作可以选择一段回文子区间删除,然后把剩下的部分拼接在一起。
问最少多少次操作可以删掉整个数组

思路:

d(i,j)表示删掉区间[i,j]的最少次数
如果a(i)==a(j),则d(i,j)=d(i+1,j-1)

code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=505;
int d[maxm][maxm];
int a[maxm];
signed main(){
    int n=re;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    for(int i=1;i<=n;i++){
        for(int j=i;j<=n;j++){
            d[i][j]=j-i+1;
        }
    }
    for(int len=2;len<=n;len++){
        for(int i=1;i<=n;i++){
            int j=i+len-1;
            if(j>n)break;
            if(len==2){
                if(a[i]==a[j]){
                    d[i][j]=1;
                }
                continue;
            }
            if(a[i]==a[j]){
                d[i][j]=d[i+1][j-1];
            }
            for(int k=i;k<j;k++){
                d[i][j]=min(d[i][j],d[i][k]+d[k+1][j]);
            }
        }
    }
    cout<<d[1][n]<<endl;
    return 0;
}

CodeForces1213 E. Array Shrinking

题意:

给一个长度为n的数组a
如果数组中存在两个位置i,j,abs(i-j)=1,且a(i)=a(j),那么可以将a(i)和a(j)合并为一个数a(i)+1
现在你可以任意进行操作的位置和顺序,问最后数组中最少剩下多少个数

思路:

d(i,j)表示[i,j]可以合并为那个数,如果不能合并则为0
用O(n3)的区间dp计算出d数组

f(i)表示前i个数最少剩下多少个数
再用O(n2)的dp配合d数组计算出f数组,f(n)就是答案

详见代码

code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=505;
int d[maxm][maxm];//d[i][j]表示[i,j]可以合并成那个数,如果不能合并则为0
int f[maxm];//f[i]表示前i个数最少能分成几个数
int a[maxm];
signed main(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
        cin>>a[i];
        d[i][i]=a[i];
	}
    for(int len=2;len<=n;len++){
        for(int i=1;i<=n;i++){
            int j=i+len-1;
            if(j>n)break;
            d[i][j]=0;
            for(int k=i;k<j;k++){
                if(d[i][k]&&d[k+1][j]&&d[i][k]==d[k+1][j]){
                    d[i][j]=d[i][k]+1;
                    break;
                }
            }
        }
    }
    if(d[1][n]){
        cout<<1<<endl;
    }else{
        for(int i=1;i<=n;i++){
            f[i]=f[i-1]+1;
            for(int j=0;j<i;j++){
                if(!d[j+1][i])continue;
                f[i]=min(f[i],f[j]+1);
            }
        }
    	cout<<f[n]<<endl;
    }
    return 0;
}

CodeForces1232 F. Clear the String

题意:

给一个长度为n的字符串,每次能删除一个子串,前提是子串的字符相同,删除之后将剩下部分拼接在一起
问最少多少次删掉整个串

思路:

d(i,j)为删除[i,j]的最少次数
1.s(i)与s(j)相同,那么d(i,j)=min(d(i+1,j),d(i,j-1)),因为s(i)=s(j),再删除s(i)的时候一定可以顺便删除s(j),删除s(j)的时候可以也可以删除s(i),所以取min即可
2.s(i)与s(j)不同,那么d(i,j)=min(d(i+1,j),d(i,j-1))+1
3.枚举分割点k,d(i,j)=min(d(i,j),d(i,k)+d(k+1,j))

code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=505;
int d[maxm][maxm];
char s[maxm];
int n;
signed main(){
    cin>>n;
    scanf("%s",s+1);
    for(int i=1;i<=n;i++)d[i][i]=1;
    for(int len=2;len<=n;len++){
        for(int i=1;i<=n;i++){
            int j=i+len-1;
            if(j>n)break;
            if(s[i]==s[j])d[i][j]=min(d[i][j-1],d[i+1][j]);
            else d[i][j]=min(d[i][j-1],d[i+1][j])+1;
            for(int k=i;k<j;k++){
                d[i][j]=min(d[i][j],d[i][k]+d[k+1][j]);
            }
        }
    }
    cout<<d[1][n]<<endl;
    return 0;
}

UVA10617 Again Palindrome

题意:

给定字符串,问有多少种删除字符的方法,使得删完之后的串是回文串

解法:
设d[i][j][i,j]的方案数
如果s[i]==s[j],那么要统计d[i+1][j]+d[i][j-1]
不过这样会把d[i+1][j-1]统计两次,
但是d[i+1][j-1]中的回文串可以加上s[i]和s[j]组成d[i+1][j-1]个回文串
因此d[i][j]=d[i+1][j]+d[i][j-1]+1;

否则d[i][j]=d[i+1][j]+d[i][j-1]-d[i+1][j-1];
code:
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
#define int long long
const int maxm=105;
int d[maxm][maxm];
char s[maxm];
int dp(int i,int j){
    if(i>j)return 0;
    if(i==j)return 1;
    if(d[i][j]!=-1)return d[i][j];
    if(s[i]==s[j])return d[i][j]=dp(i+1,j)+dp(i,j-1)+1;
    else return d[i][j]=dp(i+1,j)+dp(i,j-1)-dp(i+1,j-1);
}
signed main(){
    int T;cin>>T;
    while(T--){
        scanf("%s",s+1);
        int n=strlen(s+1);
        memset(d,-1,sizeof d);
        cout<<dp(1,n)<<endl;
    }
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值