题目链接:[NOI1995] 石子合并 - 洛谷
这道题目是那道弱化版石子合并的升级版,建议先思考明白弱化版石子合并的解题方法,我在这篇博客直接阐述如何在弱化版的基础上解决这道题,我在之前介绍过弱化版石子合并的解题方法,下面附上博客地址:P1775 石子合并(弱化版)(区间DP)_AC__dream的博客-优快云博客
这两道题的区别主要在于弱化版的石子合并问题不能合并两端的石子,而这道题可以直接合并两端的石子,当然还有一个区别就是这道题还要求出最大代价,不过原理与求最小代价基本上一模一样,甚至可以说在初始化方面还要比求最小代价还要简单,下面我来分析如何解决两端石子合并的问题:
按照原来石子合并的思考方式,对于每个f[i][j]我们是通过枚举中间值k来更新f[i][j]的,也就是说最优解一定对应一个k是在区间[i,j]上的,而这道题一开始是一个圆周,对于最优解而言也一定存在一个k在区间[1,n]上使得最优方案与链状分布k,k+1,……n,1,2,……,k-1的石子合并答案是相同的,因为对于最优方案最后一步合并我们一定是将两堆合并为一堆,而这两堆一定有一个元素是相邻的,那么相邻点就是我们要枚举的中间点,所以我们需要枚举所有中间点,最后求一个最大值一定能得到答案。但是这样做复杂度未免有点高,我们对这个问题进行如下思考:我们将原先的1~n的石子序列向右整体复制一份变为1,2……,n,1,2,……n,我们对这个石子合并问题求解长度等于n的最大值,求完之后就有f[k][k+n-1]表示序列k,k+1,……n,1,2,……,k-1的石子合并最优解,这样我们直接o(n)枚举所有的f[k][k+n-1],求一个最值即可。这个思路比较巧妙,在其他题目中也经常遇到这样的求解方法,建议大家好好理解一下,下面是代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
typedef long long ll;
const int N=303;
ll fn[N][N],fx[N][N],sum[N];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
scanf("%lld",&sum[i]),sum[i+n]=sum[i];
memset(fn,0x3f,sizeof fn);
for(int i=1;i<=2*n;i++)
fn[i][i]=0,sum[i]+=sum[i-1];
for(int len=2;len<=n;len++)
{
for(int i=1;i+len-1<=2*n;i++)
{
int j=i+len-1;
for(int k=i;k<j;k++)
{
fx[i][j]=max(fx[i][j],fx[i][k]+fx[k+1][j]+sum[j]-sum[i-1]);
fn[i][j]=min(fn[i][j],fn[i][k]+fn[k+1][j]+sum[j]-sum[i-1]);
}
}
}
ll ansmax=0,ansmin=0x3f3f3f3f3f3f3f3f;
for(int i=1;i<=n;i++)
{
ansmin=min(ansmin,fn[i][i+n-1]);
ansmax=max(ansmax,fx[i][i+n-1]);
}
printf("%lld\n",ansmin);
printf("%lld",ansmax);
return 0;
}