给定 n
个石子的 分数 wi
,且石子是 环形放置 的(即:在给定的顺序上,同时满足 1
和 n
也是相邻的)
规定每次只能合并 相邻 的两堆石子,形成一个新的石子堆,合并的 费用 是两个石子堆的 分数之和
求解两个方案:
方案一:把这 n
堆石子合并为一堆,且满足费用最大的方案
方案二:把这 n
堆石子合并为一堆,且满足费用最小的方案
本题是限制只能合并 相邻两堆石子的模型,是区间dp模板题:AcWing 282. 石子合并 的环形扩展问题。
考虑如何设定 动态规划 的阶段,既可以表示出 初始 每个石子的费用,也可以表示出 合并后 整个堆的费用
我们考虑将 当前合并的石子堆的大小 作为DP的阶段,这样 len=1
表示初值,即每个堆只有一个石子, len=n
表示终值,即一个堆中有所有的石子
这种阶段设置方法能够保证:每次合并两个区间时,它们所有的子区间合并费用都已经被计算出来了
阶段设定好后,我们考虑如何记录当前的状态,无外乎两个参数:
l
r
DP分析
状态表示—集合 f l e n , l , r flen,l,r flen,l,r :当前合并的石子堆的大小为 l e n len len,且石子堆的左端点是 l l l,右端点是 r r r 的方案
状态表示—属性 f l e n , l , r flen,l,r flen,l,r: 方案的费用最大/最小(本题两者都要求)
状态计算— f l e n , l , r flen,l,r flen,l,r:
考虑一下本题的 环形相邻 情况如何解决,方案有如下两种:
n
次,时间复杂度为 O(n^4)
2n
个堆,其中 i
和 i+n
是相同的两个堆,然后直接套 区间DP 模板,但对于 阶段 len
只枚举到 n
,根据 状态的定义,最终可以得到所求的方案,时间复杂度为 O(n^3)
初始状态: f 1 , i , i f1,i,i f1,i,i( 1 ≤ i ≤ n 1≤i≤n 1≤i≤n)
目标状态: f n , 1 , n fn,1,n fn,1,n
#include
using namespace std;
const int N = 210, M = N<<1, inf = 0x3f3f3f3f;
int n;
int f[M][M], g[M][M];
int s[M], w[M];
int main()
{
cin>>n;
for(int i=1; i<=n; ++i)
{
cin>>w[i];
w[i+n] = w[i];
}
//预处理前缀和(DP状态转移中用到的区间和)
for(int i=1; i<=n<<1; ++i)
{
s[i] = s[i-1] + w[i];
}
memset(f, 0x3f, sizeof f);
memset(g, -0x3f, sizeof g);
for(int len = 1; len <= n; ++len)//阶段:区间长度
{
for(int l = 1; l + len - 1 <= n<<1; ++l)//左端点
{
int r = l + len -1;//右端点
if(len==1) {f[l][r] = 0, g[l][r] = 0; continue;}//区间长度为1时特殊判断
for(int k = l; k < r; ++k)//枚举分界点
{
f[l][r] = min(f[l][r], f[l][k] + f[k+1][r] + s[r] - s[l-1]);
g[l][r] = max(g[l][r], g[l][k] + g[k+1][r] + s[r] - s[l-1]);
}
}
}
//目标状态中找出方案
int MIN = inf, MAX = -inf;
for(int i=1; i<=n; ++i)
{
MIN = min(MIN, f[i][i+n-1]);
MAX = max(MAX, g[i][i+n-1]);
}
cout<