洛谷P1880 石子合并 区间动归
应老师要求,悲催地第一次学习区间dp。。。
学习了动归之后才感觉到它的博大精深,好像我做不来的题都和它有关。。。
顿时觉得当时学牛二的时候被题目虐成那样也不算啥了。
题目描述
在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分.
思路:先来讲讲区间动归。区间型动态规划,又称为合并类动态规划,是线性动态规划的扩展,它在分阶段地划分问题时,与阶段中元素出现的顺序和由前一阶段的区间中哪些元素合并而来有很大的关系。如对于状态f[i,j],它表示划分的阶段为j,状态出现的位置为i,它的值取决于第i个元素出现的位置和i到j这段区间的值。这一类型的天天过后,阶段特征非常明显,求最优值时需预先设置阶段内的区间统计值,还要分动态规划的起始位置来判断。——摘自http://blog.csdn.net/zlambert/article/details/65934717
这是一道很像贪心的题目,但是显然可以找到反例,于是就要用到区间dp。我们假设f[l,r]表示从第l堆合并到第r堆的最优方案,对于每一个f[l,r],我们枚举每一个k∈[l+1..r]来比较,通过f[l,k-1]和f[k,r]合并到f[l,r]所产生的答案从而得出f[l,r]的最优解,最后的答案即为max/min(f[i,i+n-1]) (i∈[1..n])。注意处理环。
#include<iostream> #include<cstdio> #define INF 0x7fffffff #define max(a,b) (a>b?a:b) #define min(a,b) (a<b?a:b) using namespace std; int n,minn=INF,maxn=0; int a[500]; int sum[500]; long long int f[300][300]; void init() { cin >> n; for (int i=1; i <= n; i++) cin >> a[i]; for(int i=n+1;i<=2*n;++i) a[i]=a[i-n]; for (int i=1; i <= 2*n; i++) sum[i]=sum[i-1]+a[i]; } void work() { for(int i=1;i<=n*2;++i) { for(int j=1;j<=n*2;++j) f[i][j]=INF; f[i][i]=0; } for(int i=n*2-1;i>=1;--i) for(int j=i+1;j<=n*2;++j) for(int k=i;k<=j;++k) f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+sum[j]-sum[i-1]); for(int i=1;i<=n;++i) minn=min(minn,f[i][i+n-1]); cout << minn << endl; for(int i=1;i<=n*2;++i) { for(int j=1;j<=n*2;++j) f[i][j]=-INF; f[i][i]=0; } for(int i=n*2-1;i>=1;--i) for(int j=i+1;j<=n*2;++j) for(int k=i;k<j;++k) f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]+sum[j]-sum[i-1]); for(int i=1;i<=n;++i) maxn=max(maxn,f[i][i+n-1]); cout << maxn << endl; } int main() { init(); work(); return 0; }