luogu P1880 [NOI1995]石子合并

题目描述

题目链接

思路

这是一道区间 \(DP\) 的经典问题,很早就想做这道题目,可是一直没有做。

考虑最后合并为一堆石子肯定是由两堆石子合并起来得到的,然而这两堆石子也是由上面的情况得到的。所以这个问题就转化为了一个无限递归的子问题。

我们设 \(f[i][j]\) 为合并 \([i,j]\) 这些石子所花费的最小代价,所以转移就有

\[f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+sum[i][j])\ k\in[l,r) \]

其中 \(sum[i][j]\)\([i,j]\) 区间内所有石子的总花费,因为你不论怎样合并,这次合并的都要加这些代价。需要注意的是 \(k<r\) 因为如果 \(k=r\) 那么 \(k+1>r\) 就不符合条件的区间了。

然后我们就愉快地解决了这道题目。

等等,这道题目是在环上,我们可以断环为链,其实就是在将数组复制一遍,这样可以证明可以包含环上的所有情况,其实这样就将问题转化为了在 \(2n\) 合并相邻的 \(n\) 个石子的最小代价。

Code

#include<bits/stdc++.h>
using std::min;
using std::max;
const int N=2e2+10,INF=1e7+100;
int n;
int a[N];
int f[N][N],ff[N][N],sum[N][N];
int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;i++)
	{
		scanf("%d",a+i);
		a[i+n]=a[i];
	}
	for (int i=1;i<=n*2;i++)
	{
		int now=0;
		for (int j=i;j<=n*2;j++)
		{
			now+=a[j];
			sum[i][j]=now;
		}
	}
	for (int i=1;i<=n*2;i++)
	{
		for (int j=1;j<=n*2;j++)
		f[i][j]=INF;
	}
	for (int i=1;i<=n*2;i++)
	f[i][i]=0;
	for (int len=2;len<=n;len++)
	{
		for (int l=1;l<=n*2;l++)
		{
			int r=l+len-1;
			if (r>n*2) break;
			for (int k=l;k<=r-1;k++)
			{
				f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+sum[l][r]);
				ff[l][r]=max(ff[l][r],ff[l][k]+ff[k+1][r]+sum[l][r]);
			}
		}
	}
	int minn=INF,maxx=0;
	for (int i=1;i<=n*2;i++)
	{
		if (i+n-1>n*2) break;
		minn=min(minn,f[i][i+n-1]);
		maxx=max(maxx,ff[i][i+n-1]);
	}
	printf("%d\n%d\n",minn,maxx);
	return 0;
}
posted @ 2019-09-19 16:09  准点的星辰  阅读(120)  评论(0编辑  收藏  举报