P1880 [NOI1995]石子合并[环形DP]

 题目来源:洛谷

题目描述

在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。

试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分

输入输出格式

输入格式:

数据的第1行试正整数N,1≤N≤100,表示有N堆石子.第2行有N个数,分别表示每堆石子的个数.

输出格式:

输出共2行,第1行为最小得分,第2行为最大得分.

 

输入输出样例

 

输入样例#1: 
4
4 5 9 4
输出样例#1: 
43
54

 

解析:

这是一道相当经典的四边形不等式和环形dp,以至于一道NOI级别的题目被研究成了绿题。。。

 


 

首先题目意思很清楚,很容易看出来这就是个首尾相接的环。
 
 
我们先想一下如果不是环,而是一条链怎么做。
 
dp的思路很容易想出来,就是区间dp的思想,我们将将要合并的两堆石子的距离当做阶段,合并哪两堆石子作为状态,决策就是选择哪两堆石子合并。
 
我们定义dp[l][r]表示合并l~r之间的石子所需耗费的最小/大费用,其中l表示左端点的石子,r表示右端点的石子。
 
我们可以把每个l~r的状态看做两个子问题,假设k为l~r之间的任意一堆石子,我们就要使得l~k的答案和k+1~r的答案最优。因此我们枚举l~r之间的每个位置k,并寻找使得结果最优的一个k。
对应到动态规划中,就意味着两个较小的区间向一个更长的区间转移,k点不仅是两个小区间的划分点,也是转移的决策。
 
比方说如果我们求最小值,得出这样的状态转移方程:

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 }

 

 

 

posted @ 2019-06-21 18:02  DarkValkyrie  阅读(463)  评论(0编辑  收藏  举报