[区间dp]题解 P1880 石子合并

[NOI1995] 石子合并

题目描述

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

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

输入格式

数据的第 \(1\) 行是正整数 \(N\),表示有 \(N\) 堆石子。

\(2\) 行有 \(N\) 个整数,第 \(i\) 个整数 \(a_i\) 表示第 \(i\) 堆石子的个数。

输出格式

输出共 \(2\) 行,第 \(1\) 行为最小得分,第 \(2\) 行为最大得分。

样例 #1

样例输入 #1

4
4 5 9 4

样例输出 #1

43
54

提示

\(1\leq N\leq 100\)\(0\leq a_i\leq 20\)

思路

最近一直再做一些 dp 的题目,那么这道经典的区间 dp 当然是不能放过,这里写一下此题的题解。

区间 dp 定义状态这一步还是比较简单的,首先肯定能想到定义一个 \(dp_{l,r}\) 这样的状态表示,然后我们只需要,思考一下如何转移方程就行了。

还是套路的枚举一下间断点在哪里。假如是 \(k\)

突然发现我们需要预处理一个数组 \(sum_{l,r}\) 表示 \(l\)\(r\) 中数字的和。这样的话我们就很好办了。反正总共的时间复杂度已经大于 \(\mathbb{O}(n^2)\) 了,那么我们这样预处理也问题不大,反正也不会让时间复杂度再增大到哪里去。当然是可以通过前缀和优化到 \(\mathbb{O}(n)\) 的。

状态转移方程显然为:

这里我们只写求最大值的,最小值的相反,但是要注意处理一下初值,我在这卡了 20min 。
\(dp_{l,r}=max\{dp_{l,k}+dp_{k+1,r}+sum_{l,k}+sum_{k+1,r}\} k \in [l,r)\)
非常显然,把两个东西合并起来所需要的代价加起来就 ok 了。

代码

#include <bits/stdc++.h>
#define debug puts("I love Newhanser forever!!!!!");
#define pb push_back
using namespace std;
template <typename T>inline void read(T& t){
    t=0; register char ch=getchar(); register int fflag=1;
    while(!('0'<=ch&&ch<='9')){if(ch=='-') fflag=-1;ch=getchar();}
    while(('0'<=ch&&ch<='9')){t=t*10+ch-'0'; ch=getchar();} t*=fflag;
}
template <typename T,typename... Args> inline void read(T& t, Args&... args){read(t);read(args...);}
const int MAXN=386;
int n,a[MAXN];
int dp[MAXN][MAXN],sum[MAXN][MAXN],maxn,f[MAXN][MAXN],minn=0x3f3f3f3f;
int main(){
	read(n);
	memset(f,0x3f,sizeof(f));
	for(int i=1;i<=n;++i) read(a[i]),a[i+n]=a[i],sum[i][i]=a[i],f[i][i]=0,f[i+n][i+n]=0,sum[i+n][i+n]=a[i];
	for(int len=1;len<=n;++len)
		for(int l=1;l<n*2-len;++l){
			int r=l+len;
			sum[l][r]=sum[l][r-1]+a[r];
		}
	for(int len=1;len<=n;++len)
		for(int l=1;l<n*2-len;++l){
			int r=l+len;
			for(int k=l;k<r;++k){
				f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+sum[l][k]+sum[k+1][r]);
				dp[l][r]=max(dp[l][r],dp[l][k]+dp[k+1][r]+sum[l][k]+sum[k+1][r]);
			}
		}
	for(int i=1;i<=n;++i) maxn=max(maxn,dp[i][i+n-1]);
	for(int i=1;i<=n;++i) minn=min(minn,f[i][i+n-1]);
	cout<<minn<<endl<<maxn<<endl;
    return 0;
}
//Welcome back,Chtholly.
posted @ 2022-06-27 20:23  Mercury_City  阅读(45)  评论(0编辑  收藏  举报