【算法学习】区间动态规划
区间型动态规划
区间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:涂色
- 确定状态:
最后一步:将最后一块涂色
子问题:子区间的最小涂色数目 - 状态方程:dp[i][j] 从i到j一段区间的最小涂色数目
- 转移顺序:从小区间到大区间
转移方式:
我们研究i,j两个区间端点的性质(因为大区间的边界是从小区间扩展来的)- i和j的颜色相等:
这时就可以看作在涂区间[i+1,j]时候向左多涂了一格,或在涂区间[i,j-1]时候向右多涂了一格
dp[i][j]=min(dp[i+1][j],dp[i][j-1]); - i和j的颜色不相等:
这时就要左右区间分别涂色,枚举分界点
f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]);
- i和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- 若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']);
- 若i,j相同,则不需要花费,问题转化为求子区间[i+1,j-1]
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
矩阵连乘