区间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问题总结的主要内容,如果未能解决你的问题,请参考以下文章

区间dp总结

区间DP题目总结

区间DP总结

区间dp的一些模式和总结

1024. 视频拼接 dp

区间DP小结(附经典例题) 转载