P1880 石子合并
题目描述
在一个园形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分.
输入输出格式
输入格式:数据的第1行试正整数N,1≤N≤100,表示有N堆石子.第2行有N个数,分别表示每堆石子的个数.
输出格式:输出共2行,第1行为最小得分,第2行为最大得分.
输入输出样例
这是一道区间dp十分经典的模板题,让我们揣测一下,前辈们是如何得到这个状态转移方程的。
首先,要计算合并的最大值、最小值,既然是动态规划,我们需要洞悉其中一些关联且确定的状态。
以下以最大值为例。
既然是最大值,那么求得的结果是否满足每一区间都是该区间所能达得到的的最大值?
显然是这样的。反证法:倘若有一个区间不是,那么换做该区间取得最大值的方案,最终结果将比原得分大。显然必定满足任意区间得分一定是该区间内的最大值。
这样我们可以定义状态f[i][j],表示i到j合并后的最大得分。其中1<=i<=j<=N。
既然这样,我们就需要将这一圈石子分割。很显然,我们需要枚举一个k,来作为这一圈石子的分割线。
这样我们就能得到状态转移方程:
f[i][j] = max(f[i][k] + f[k+1][j] + d(i,j));其中,1<=i<=<=k<j<=N。
d(i,j)表示从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。这样,就能保证地推的正确。
上代码。
#include<iostream> #include<cstdio> #include<cmath> using namespace std; int n,minl,maxl,f1[300][300],f2[300][300],num[300]; int s[300]; inline int d(int i,int j){return s[j]-s[i-1];} //转移方程:f[i][j] = max(f[i][k]+f[k+1][j]+d[i][j]; int main() { scanf("%d",&n); for(int i=1;i<=n+n;i++) { scanf("%d",&num[i]); num[i+n]=num[i]; s[i]=s[i-1]+num[i]; } for(int p=1;p<n;p++) { for(int i=1,j=i+p;(j<n+n) && (i<n+n);i++,j=i+p) { f2[i][j]=999999999; for(int k=i;k<j;k++) { f1[i][j] = max(f1[i][j], f1[i][k]+f1[k+1][j]+d(i,j)); f2[i][j] = min(f2[i][j], f2[i][k]+f2[k+1][j]+d(i,j)); } } } minl=999999999; for(int i=1;i<=n;i++) { maxl=max(maxl,f1[i][i+n-1]); minl=min(minl,f2[i][i+n-1]); } printf("%d %d",minl,maxl); return 0; }
边界状态还望读者仔细思考(不懂请留言)
放寒假了,是时候刷一波dp过过瘾了。