题目来源:洛谷
题目描述
在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分
输入输出格式
输入格式:
数据的第1行试正整数N,1≤N≤100,表示有N堆石子.第2行有N个数,分别表示每堆石子的个数.
输出格式:
输出共2行,第1行为最小得分,第2行为最大得分.
输入输出样例
解析:
这是一道相当经典的四边形不等式和环形dp,以至于一道NOI级别的题目被研究成了绿题。。。
首先题目意思很清楚,很容易看出来这就是个首尾相接的环。
dp[l][r]=min(1<=l<=n-len+1,l<r<=n,l<=k<r){dp[l][k]+dp[k+1][r]+w(l,r)};
其中w(l,r)函数表示合并l~r之间的石子的花费,len表示区间长度
这个东西叫二维四边形不等式,常用于优化区间dp,详细情况我这里就不讲了(我懒),如果有需要可以百度或者戳这里看dalao博客。
PS:关于初始化,我们知道每个石子堆移到它自己上面花费是0,所以。。。
思考一下为什么每次决策增加的是区间和:移动石子堆的花费是可叠加的,而且费用是会重叠的,如果我们要合并l~r之间的所有石子,那么显然无论它之前是由哪两堆合并而来的,花费都是sum[r]-sum[l-1](sum为前缀和数组)。
关于w(l,r)函数,我们可以用前缀和优化搞定,于是我们需要一个三重循环来解决此问题,复杂度也就差不多达到了O(n^3),这是无法承受大规模数据的。
动态规划有一种基本的优化思路,就是如果一个状态的某个维度可以由其他维度推出,那么我们直接在每循环的时候把它算出来就得了。
在这里,如果我们知道了左端点l和区间长度len的话,那么r就是可求得的,即r=l+len-1;
至此,链式dp的思路就讲完了。
你如果拿这个思路做这道题,会发现过不了(WA4个点),这是因为题目说了是环。
那环跟链有啥区别呢?
很容易发现,如果是环形的话,某两个点之间的距离可以有两种可能。
举个例子,我们假设石子总堆数为n,对于第一堆石子到最后一堆石子之间的距离len,如果是链式的话,只有n-1这种可能;而如果是环形的话,我们就有n-1和1两种可能。也就是说,每个石子堆之间都有两个距离(当然可以相等),那我们怎么设计dp算法呢?
有一种普遍的解法,就是把环拆开成相连的两条相同的链,然后在1~2n上跑dp。具体来说其实就是把上面讲到的两个距离拆出来分开处理,某个点l到第一条链有一个最优解,到第二条链又有一个最优解,我们再取这两个最优解的最优解。
所以之前的步骤我们可以参考链式dp写出来,而寻找两个最优解中的最优解,我们需要再分析一下。
链式的解法最优解就在dp[1][n],即把1~n堆石子全部合并。注意,这种情况下有且仅有1~n之间的距离为n-1。
然而如果这是一个环,就有n个n-1的距离,也就是有n个局部最优解,我们还要在这些最优解里面找全局最优解,这也是本题的又一个关键点。
所以实际上我们扫一遍dp[i][i+n-1](1<=i<=n),找到最优解就行了。
参考代码:
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #define N 1010 5 #define INF 0x3f3f3f3f 6 using namespace std; 7 int a[N<<1],dp1[N][N],dp2[N][N],s[N]; 8 int main() 9 { 10 int n; 11 cin>>n; 12 for(int i=1;i<=n;i++) scanf("%d",&a[i]),a[i+n]=a[i]; 13 memset(dp1,0x3f,sizeof(dp1)); 14 memset(dp2,0xff,sizeof(dp2)); 15 for(int i=1;i<=n+n;i++){ 16 dp1[i][i]=0;dp2[i][i]=0; 17 s[i]=s[i-1]+a[i]; 18 } 19 for(int len=2;len<=n*2;len++) 20 for(int l=1;l<=n*2-len+1;l++) 21 { 22 int r=l+len-1; 23 for(int k=l;k<r;k++) 24 { 25 dp1[l][r]=min(dp1[l][r],dp1[l][k]+dp1[k+1][r]+(s[r]-s[l-1])); 26 dp2[l][r]=max(dp2[l][r],dp2[l][k]+dp2[k+1][r]+(s[r]-s[l-1])); 27 } 28 } 29 int maxx=-INF,minn=INF; 30 for(int i=1;i<=n;i++){ 31 maxx=max(maxx,dp2[i][i+n-1]); 32 minn=min(minn,dp1[i][i+n-1]); 33 } 34 cout<<minn<<endl; 35 cout<<maxx<<endl; 36 return 0; 37 }