区间DP问题总结
Posted Harris-H
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了区间DP问题总结相关的知识,希望对你有一定的参考价值。
区间DP问题总结
1.石子合并类
k e y key key:枚举中间点 k k k合并
P1880 [NOI1995] 石子合并
每次合并,增加的贡献为合并后的区间和。
for(int l=2;l<=n;l++)
for(int i=1;i+l-1<2*n;i++){
int j=i+l-1;
for(int k=i;k<j;k++){
f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]+sum[i][j]);
g[i][j]=min(g[i][j],g[i][k]+g[k+1][j]+sum[i][j]);
}
}
P3146 [USACO16OPEN]248 G
贡献相同的区间才可合并,合并后贡献+1。
for(int i=2,len=2;i<=n;i++,len++)
for(int l=1,r=l+len-1;r<=n;l++,r++)
for(int k=l;k<r;k++)
{
if(dp[l][k]==dp[k+1][r]&&dp[l][k])
{
dp[l][r]=max(dp[l][r],dp[l][k]+1);
ans=max(dp[l][r],ans);
}
}
POJ-2955 Brackets
统计最大括号子序列长度。
for(int l=2;l<=n;l++)
for(int i=1,j=i+l-1;j<=n;i++,j++){
if((s[i]=='('&&s[j]==')')||(s[i]=='['&&s[j]==']'))
dp[i][j]=dp[i+1][j-1]+2;
for(int k=i;k<j;k++)
dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]);
}
#10149. 「一本通 5.1 例 3」凸多边形的划分
类似石子合并更新即可。注意高精度
if __name__ == '__main__':
N = int(2e3 + 5)
n = int(input())
a = [0]
a.extend(list(map(int, input().split())))
f = [[1 << 126 for i in range(N)] for j in range(N)]
for i in range(1, n + 1):
f[i][i + 1] = 0
for l in range(3, n + 1):
i, j = 1, l
while j <= n:
for k in range(i + 1, j):
f[i][j] = min(f[i][j], f[i][k] + f[k][j] + a[i] * a[k] * a[j])
i += 1
j += 1
print(f[1][n])
c++代码
read(n);for(int i=1;i<=n;i++) read(a[i]);
mst(f,0x3f);
for(int i=1;i<=n;i++) f[i][i+1]=0;
for(int l=3;l<=n;l++)
for(int i=1,j=l;j<=n;i++,j++){
for(int k=i+1;k<j;k++)
f[i][j]=min(f[i][j],f[i][k]+f[k][j]+a[i]*a[k]*a[j]);
}
write(f[1][n]);
POJ 1651Multiplication Puzzle
经典石子合并,只不过是贡献计算不一样。
for(int i=1;i<=n;i++) f[i][i+1]=0;
for(int l=3;l<=n;l++)
for(int i=1,j=l;j<=n;i++,j++){
for(int k=i+1;k<j;k++)
{
f[i][j]=min(f[i][j],f[i][k]+f[k][j]+a[i]*a[k]*a[j]);
}
}
2.取两端合并
key:用左端或者右端合并区间
P1005 [NOIP2007 提高组] 矩阵取数游戏
每次只能取左端或者右端。
for(i=0;i<m;i++)
for(j=1;i+j<=m;j++)
f[j][i+j]=max(2*(f[j+1][i+j]+sum[j]),2*(f[j][i+j-1]+sum[i+j]));
P3205 [HNOI2010]合唱队
在题意限制下,还需要维护这个人从左端还是右端过来的。也就是左端只能上个状态的右端更新过来。
for(int l=2;l<=n;l++)
for(int i=1;i+l-1<=n;i++){
int j=i+l-1;
if(a[i]<a[i+1]) f[i][j][0]+=f[i+1][j][0];
if(a[i]<a[j]) f[i][j][0]+=f[i+1][j][1];
if(a[j]>a[j-1]) f[i][j][1]+=f[i][j-1][1];
if(a[j]>a[i]) f[i][j][1]+=f[i][j-1][0];
f[i][j][0]%=mod,f[i][j][1]%=mod;
}
POJ 3280 Cheapest Palindrome
回文串,四种情况:删左、右,增左、右。若左右相等则直接更新。
for(int l=2;l<=m;l++)
for(int i=1,j=i+l-1;j<=m;i++,j++){
if(a[i]==a[j]) f[i][j]=f[i+1][j-1];
else f[i][j]=min(min(f[i+1][j]+del[a[i]],f[i][j-1]+del[a[j]]),min(f[i+1][j]+add[a[i]],f[i][j-1]+add[a[j]]));
}
ZhimaOI #520 队列
每次只能从两端取,但是 是博弈问题,所以维护两个dp。
一个表示当前状态下先手取得最大值,一个表示当前状态后手取,先手的最小值。
for(int l=2;l<=n;l++)
for(int i=1,j=i+l-1;j<=n;i++,j++){
f[i][j]=max(g[i+1][j]+a[i],g[i][j-1]+a[j]);
g[i][j]=min(f[i+1][j],f[i][j-1]);
}
P1220 关路灯
需要维护当前到达了区间 [ i , j ] [i,j] [i,j]的 i i i还是 j j j,然后就是取两端套路更新了。
for(int l=2;l<=n;l++)
for(int i=1,j=i+l-1;j<=n;i++,j++){
f[i][j][0]=min(f[i][j][0],f[i+1][j][0]+(a[i+1]-a[i])*(s[n]-s[j]+s[i]));
f[i][j][0]=min(f[i][j][0],f[i+1][j][1]+(a[j]-a[i])*(s[n]-s[j]+s[i]));
f[i][j][1]=min(f[i][j][1],f[i][j-1][1]+(a[j]-a[j-1])*(s[n]-s[j-1]+s[i-1]));
f[i][j][1]=min(f[i][j][1],f[i][j-1][0]+(a[j]-a[i])*(s[n]-s[j-1]+s[i-1]));
}
P2858 [USACO06FEB]Treats for the Cows G/S
比较水的取两端,没啥限制。
for(int l=1;l<=n;l++)
for(int i=1,j=i+l-1;j<=n;i++,j++){
f[i][j]=max(f[i+1][j]+a[i]*(n-l+1),f[i][j-1]+a[j]*(n-l+1));
}
以上是关于区间DP问题总结的主要内容,如果未能解决你的问题,请参考以下文章