区间DP
Posted hzjuruo
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了区间DP相关的知识,希望对你有一定的参考价值。
快联赛了,去年寒假学的东西,现在考一次死一次,所以正好把模拟题里的都拽出来,总结一下
区间DP一般多出现在相邻集合的合并的问题上,贡献都是集合之间的,由于是相邻集合的合并,所以可以看做一段区间
模拟106T1
其实是挺裸的一道题,但是考场上就是没想出来,考虑合并的过程,由于只能是相邻的两个集合合并,那对于每个时刻的状态,已经合并起来的集合一定对应的是最初的集合的某一个区间段,那么设$f[i][j]$代表把$i$到$j$这个区间的所有集合合并成一个集合,因为一个集合肯定是被两个集合合起来的,所以就有转移方程$f[i][j]=max(f[i][k]+f[k+1][j]+g[i][k]*g[k+1][j])$,$k$是两个被合并的集合中间断开的位置,$g[i][j]$代表把$i$到$j$这个区间全部合并起来之后的集合大小,可以用$bitset$预处理出来,复杂度$O(n^3)$
对于他说的一个环,直接把环变成2倍的链进行上面的过程就可以了,最后统计答案的时候只需要枚举断点
1 #include<iostream> 2 #include<cstdio> 3 #include<bitset> 4 #define maxn 666 5 using namespace std; 6 int n,ans; 7 int g[maxn][maxn],f[maxn][maxn]; 8 bitset <maxn> S[maxn]; 9 int main() 10 { 11 // freopen("1.du","r",stdin); 12 // freopen("w.out","w",stdout); 13 freopen("merge.in","r",stdin); 14 freopen("merge.out","w",stdout); 15 scanf("%d",&n); 16 for(int i=1;i<=n;++i) {int a; scanf("%d",&a); S[i][a]=1; S[i+n][a]=1;} 17 for(int i=1;i<=2*n;++i) 18 { 19 bitset <maxn> ls; ls.reset(); 20 for(int j=i;j<=2*n;++j) {ls|=S[j]; g[i][j]=ls.count();} 21 } 22 for(int len=1;len<=n;++len) 23 for(int i=1;i<=2*n;++i) 24 { 25 int j=i+len-1; 26 if(j>2*n) break; 27 for(int k=i;k<j;++k) f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]+g[i][k]*g[k+1][j]); 28 } 29 for(int i=1;i<=n;++i) ans=max(ans,f[i][i+n-1]); 30 printf("%lld ",ans); 31 fclose(stdin); 32 fclose(stdout); 33 return 0; 34 }
模拟测试93T2
似乎这种二叉搜索树形态的问题都多多少少可以扯上区间DP,因为二叉搜索树的一个性质是左子树的值全部小于根节点,右子树的值全部大于根节点,反过来说所有大于根节点的值都在他的左子树里,所有大于根节点的值都在他的右子树里,那么他的左右子树都分别对应一段连续的区间
对于这道题,我们设$f[i][j]$为$i$到$j$这个区间形成了一颗子树,到这颗子树的根节点的最小耗时,那么就有转移方程$f[i][j]=min(f[i][k-1]+f[k+1][j]+s[j]-s[i-1])$,$s[i]$是$x$数组的前缀和,$k$是枚举的$i$到$j$这个区间形成的子树的根节点,复杂度$O(n^3)$
暴力的复杂度不够优秀,考虑能不能进行优化,我们不可能可以发现关于根节点的决策是有单调性的,以下均为口胡没有正确性保障因为新加进来一个点,如果他要放在右子树里,那么你把根节点左移肯定不会更优,同理,如果他需要在左子树里,你把根节点右移也不会变右,如果这样的话,那这次决策的最有决策点就一定在你合并的那两个子树的最优决策点之间,所以在转移过程中,记录最优决策点进行转移就可以了,复杂度$O(n^2)$,关于复杂度的证明,甩锅$ooo$
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 #define maxn 5050 5 #define ll long long 6 using namespace std; 7 int n; 8 int x[maxn]; 9 ll qz[maxn]; 10 int g[maxn][maxn]; 11 ll f[maxn][maxn]; 12 int main() 13 { 14 scanf("%d",&n); memset(f,0x3f,sizeof(f)); 15 for(int i=1;i<=n;++i) {scanf("%d",&x[i]); qz[i]=qz[i-1]+x[i]; f[i][i]=x[i]; g[i][i]=i;} 16 for(int len=2;len<=n;++len) 17 for(int i=1;i<=n;++i) 18 { 19 int j=i+len-1; 20 if(j>n) continue; 21 for(int k=g[i][j-1];k<=g[i+1][j];++k) 22 { 23 if(k==i) 24 { 25 if(f[k+1][j]+qz[j]-qz[i-1]<f[i][j]) 26 { 27 f[i][j]=f[k+1][j]+qz[j]-qz[i-1]; 28 g[i][j]=k; 29 } 30 } 31 else if(k==j) 32 { 33 if(f[i][k-1]+qz[j]-qz[i-1]<f[i][j]) 34 { 35 f[i][j]=f[i][k-1]+qz[j]-qz[i-1]; 36 g[i][j]=k; 37 } 38 } 39 else 40 { 41 if(f[i][k-1]+f[k+1][j]+qz[j]-qz[i-1]<f[i][j]) 42 { 43 f[i][j]=f[i][k-1]+f[k+1][j]+qz[j]-qz[i-1]; 44 g[i][j]=k; 45 } 46 } 47 } 48 } 49 printf("%lld ",f[1][n]); 50 return 0; 51 }
模拟测试36T2
之前写过题解,刚说的第一道挺像的,似乎还更麻烦了一些,就直接贴我之前写过的了
1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 #define maxn 4010 5 #define ll long long 6 ll n,head,tail; 7 ll a[maxn],ma; 8 ll dp[maxn][maxn]; 9 int main() 10 { 11 scanf("%d",&n); 12 for(int i=1;i<=n;++i) 13 { 14 scanf("%lld",&a[i]); a[i+n]=a[i]; 15 dp[i][i]=a[i]; dp[i+n][i+n]=a[i]; 16 } 17 a[0]=a[n]; 18 for(int cd=1;cd<n;++cd) 19 for(head=1;head<=2*n;++head)//i+cd-1是尾 20 { 21 tail=head+cd-1; 22 if(tail>2*n) continue; 23 if(cd&1) //奇 24 { 25 if(a[head-1]>a[tail+1]) 26 { 27 if(dp[head][tail]>dp[head-1][tail]) 28 dp[head-1][tail]=dp[head][tail]; 29 } 30 else 31 { 32 if(dp[head][tail]>dp[head][tail+1]) 33 dp[head][tail+1]=dp[head][tail]; 34 } 35 } 36 else //偶 37 { 38 if(dp[head][tail]+a[head-1]>dp[head-1][tail]) 39 dp[head-1][tail]=dp[head][tail]+a[head-1]; 40 if(dp[head][tail]+a[tail+1]>dp[head][tail+1]) 41 dp[head][tail+1]=dp[head][tail]+a[tail+1]; 42 } 43 } 44 printf("%lld",ma); 45 return 0; 46 }
把模拟测试翻了翻似乎100多场也就这三道题,确实是场场挂,考后看到题解说是区间DP就能打出来了,所以说一些关于相邻两个集合什么之类的合并一定要想到有区间DP这个东西
以上是关于区间DP的主要内容,如果未能解决你的问题,请参考以下文章