Luogu 1880 合并石子

Luogu 1880 合并石子 (线性动态规划)

经典的区间型动态规划

——————————————————————————————————
题干:https://www.luogu.org/problem/P1880

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

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

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

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

样例:
输入:
4
4 5 9 4
输出:
43
54
——————————————————————————————————
思路:

1.预处理前缀和:
先进行预处理,因为之后要用到i—j的石子和,所以先统计前缀和,a[j]-a[i-1]即为i—j的石子和,因为环状结构,所以处理到2*n;

2.最优子结构:
既然要得到最优解(以最大值为例),必须每个区间合并都是最优解,假设有
一个区间不是最大得分,那这个区间更改为最大得分,最终结果一定比原结果
更优,满足最优子结构。

3.状态:
既然是线性,那我们不妨定义dp[i][j]为从i合并到j的最大得分(最小得分)。

4.状态转移方程:
那我们如何得出状态转移方程: 可以枚举一个断点k,把一段长区间化为短区间,状态转移方程很明显:

dp[i][j]=max(dp[i][k]+dp[k+1][j]+a[j]-a[i-1]).

5.转移顺序:
这道题的状态转移方程很好想,但转移顺序确实一个难点,假设正着循环,枚举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]没有结果.

所以我们需要枚举区间长度,左右端点,保证先把长度为2的合并,
再合并3 to n,这样就可保证已经确定过所需状态。

6.环形结构如何处理:因为是环形结构 ,所以我们 在后面复制一遍前面的数,把石子数扩展到2 * n;

例: 我们以样例为例子:
4 5 9 4 —> 4 5 9 4 4 5 9 4
此时便包含了所有情况
(4 5 9 4)4 5 9 4
4(5 9 4 4)5 9 4
4 5 (9 4 4 5)9 4
4 5 9 (4 4 5 9)4
正确性显然;

7.结果: 最后只需统计长度为n区间内的最优解即可。

——————————————————————————————————

代码:

#include
#include
#include
#include
#include
using namespace std;
const int MAXN = 300 + 10; 
int n,ansmin,ansmax,dp1[MAXN][MAXN],dp2[MAXN][MAXN];//用dp1数组记录i--j的最大得分,用dp2数组记录i--j的最小得分 
int a[MAXN]; //用a数组记录前缀和,用a[j]-a[i-1]得到i--j的和。 
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		a[i+n]=a[i];
		a[i]+=a[i-1]; 
	}
	for(int i=n+1;i<=2*n;i++)
	a[i]+=a[i-1]; //环状结构 ,所以开到2*n 
	for(int p=1;p<n;p++)  //区间长度
    {  
        for(int i=1,j=i+p;(j<n+n) && (i<n+n);i++,j=i+p)  //左右端点
        {  
            dp2[i][j]=999999999;  
            for(int k=i;k<j;k++)   //断点
            {  
                dp1[i][j] = max(dp1[i][j], dp1[i][k]+dp1[k+1][j]+a[j]-a[i-1]);   
                dp2[i][j] = min(dp2[i][j], dp2[i][k]+dp2[k+1][j]+a[j]-a[i-1]);  
                //状态转移方程 dp[i][j]=max(dp[i][k]+dp[k+1][j]+a[j]-a[i-1]); 
            }  
        }  
    }  
	ansmin=1e9+7;
	ansmax=0;
	for(int i=1;i<=n;i++)
	{
		ansmax=max(ansmax,dp1[i][i+n-1]);
		ansmin=min(ansmin,dp2[i][i+n-1]);
	}//最后统计长度为n区间的最大得分的最大值 ,最小得分的最小值 
	cout<<ansmin<<"\n"<<ansmax;
	return 0;
}

2019.10.26

By October

你可能感兴趣的:(区间dp,dp)