P1880 [NOI1995] 石子合并
此篇题解写给像我一样的蒟蒻。
题目描述
在一个圆形操场的四周摆放 N 堆石子,现要将石子有次序地合并成一堆,规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
试设计出一个算法,计算出将 N 堆石子合并成 1 堆的最小得分和最大得分。
输入格式
数据的第 1 行是正整数 N,表示有 N 堆石子。
第 2 行有 N 个整数,第 i 个整数 ai 表示第 i 堆石子的个数。
输出格式
输出共 2 行,第 1 行为最小得分,第 2 行为最大得分。
输入输出样例
输入 #1复制
4 4 5 9 4
输出 #1复制
43 54
说明/提示
1≤N≤100,200≤ai≤20。
问题分析:
第一步
对于石子合并,因为可以从任意一对相邻的石子开始合并,所以要理解什么是破环成链。
对于破环成链,我们对4 5 9 4这组数据进行分析
首先我们知道在一个环上,4 5 9 4可以分布在不同位置,如
5 9
4 9 5 4
4 4
如果我们对于数字分布位置不相同的环进行相同划分,如
\5 \9
4 \ 9 5 \ 4
4\ 4\
那我们划分出来的是(5 9)(4 4)一组和(9 4)(4 5)一组,破环成链,就是方便我们进行枚举这种问题。我们用一个两倍的数组a存石子数,然后对于2*n的区间,我们去枚举这么一个长度为n的区间就可以表示环上摆放不同位置的数了。前缀和O(1)求出第i--第j堆石子最后一次合并所得分数。
第二步
得出转移方程。
我们用f[i][j]表示从第i堆合并到第j堆石子所用最小得分。
那么我们枚举 k 从 i-->j 范围做一个划分,即最后合并(i,k)与(k+1,j)两堆石子,从而得出转移方程
f[i][j]=min(f[i][k]+f[k+1][j]+sum[j]-sum[i-1])
我们需要枚举左端点 i 和 右端点 j 以及划分点 k,但对于转移方程f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+sum[j]-sum[i-1])
我们对于左端点为1 右端点为 3 划分点为1分析,
f[1][3] = min(f[1][3],f[1][1]+f[2][3]+sum[3]-sum[0]) 然而我们在此之前并没有求出f[2][3]
所以我们得出应该先枚举右端点,然后左端点从大到小进行枚举即可
最后到了快乐的写代码环节。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 250;//很吉利
int a[MAXN],sum[MAXN],f[MAXN][MAXN],d[MAXN][MAXN];
int n,maxn=-0x3f3f3f3f,minn=0x3f3f3f3f;
void dp(int p)
{
for(int i=1+p;i<=n+p;i++)
for(int j=i;j<=n+p;j++)
f[i][j]=0x3f3f3f3f,d[i][j]=0;
for(int i=1+p;i<=n+p;i++) f[i][i]=d[i][i]=0;//初始化
for(int j=1+p;j<=n+p;j++)
for(int i=j;i>=1+p;i--)//逆序枚举
for(int k=i;k<j;k++)
{
f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+sum[j]-sum[i-1]);
d[i][j]=max(d[i][j],d[i][k]+d[k+1][j]+sum[j]-sum[i-1]);
}
minn=min(minn,f[1+p][n+p]);
maxn=max(maxn,d[1+p][n+p]);
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
a[i+n]=a[i];
sum[i]=sum[i-1]+a[i];
}
for(int i=n+1;i<=2*n;i++) sum[i]=sum[i-1]+a[i];
for(int i=0;i<n;i++)//破环成链 枚举区间
{
dp(i);
}
cout<<minn<<endl<<maxn<<endl;
return 0;
}