区间dp模板题
P1880 [NOI1995]石子合并
先上状态转移方程
mindp[i][j]=min(mindp[i][k]+mindp[k+1][j]+d(i,j),mindp[i][j]);
maxdp[i][j]=max(maxdp[i][k]+maxdp[k+1][j]+d(i,j),maxdp[i][j]);
其中d(i,j)意义为从第i堆加到第j堆的个数和
mindp[i][j]代表第i堆到第j堆的合并最小分数
maxdp[i][j]代表第i堆到第j堆的合并最大分数
到了这里 其实这道题就应该迎刃而解了 但是我还是卡了很久
原因是什么呢
这道题 是TM环状
所以我们需要将环拆开 变成一个2n长度的链
讲到这里 题应该已经结束了 但是还有一个关键点
就是 我们应该怎么遍历k呢
先放代码
#define rep(i,a,b) for(int i = a;i <= b;i++) rep(l,2,n){ rep(i,1,2*n-l+1){ int j = i+l-1; mindp[i][j] = 1000000000; maxdp[i][j] = 0; rep(k,i,j-1){ mindp[i][j] = min(mindp[i][k]+mindp[k+1][j]+d(i,j),mindp[i][j]); maxdp[i][j] = max(maxdp[i][k]+maxdp[k+1][j]+d(i,j),maxdp[i][j]); } } }
在考虑如何递推时,通常考虑如下几个方面:
是否能覆盖全部状态?
求解后面状态时是否保证前面状态已经确定?
是否修改了已经确定的状态?
也就是说,在考虑递推顺序时,务必参考动态规划的适应对象多具有的性质,具体参考《算法导论》相关或百度百科或wiki。
既然之前说过我们需要枚举k来划分i和j,那么如果通过枚举i和j进行状态转移,很显然某些k值时并不能保证已经确定过所需状态。
如,i=1 to 10,j=1 to 10,k=1 to 9.当i=1,j=5,k=3时,显然状态f[k+1][j]没有结果。
那么,我们是不是应该考虑枚举k?
但这样i和j就难以确定了。
我们不难得到一个两全的方法:枚举j-i,并在j-i中枚举k。这样,就能保证地推的正确。
ac代码
#include "cstdio" #include "iostream" #include "algorithm" #include "string.h" #define rep(i,a,b) for(int i = a;i <= b;i++) using namespace std; const int e = 210; int n; int k; int maxdp[e][e],mindp[e][e]; int psum[e]; int T[e]; inline int d(int i,int j){ return psum[j]-psum[i-1]; } int main(){ cin >> n; rep(i,1,n){ cin >> T[i]; T[i+n]=T[i]; } rep(i,1,2*n){ psum[i]=psum[i-1]+T[i]; } rep(l,2,n){ rep(i,1,2*n-l+1){ int j = i+l-1; mindp[i][j] = 1000000000; maxdp[i][j] = 0; rep(k,i,j-1){ mindp[i][j] = min(mindp[i][k]+mindp[k+1][j]+d(i,j),mindp[i][j]); maxdp[i][j] = max(maxdp[i][k]+maxdp[k+1][j]+d(i,j),maxdp[i][j]); } } } int minn = 1000000000,maxx = 0; rep(i,1,n){ minn = min(minn,mindp[i][i+n-1]); maxx = max(maxx,maxdp[i][i+n-1]); } printf("%d %d",minn,maxx); return 0; }