环形石子合并问题(动态规划)
传统的石子合并问题为:有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动相邻的2堆石子合并,合并花费为新合成的一堆石子的数量,求将这N堆石子合并成一堆的总花费最小(或最大)。
这类问题类似区间DP的解法,设dp[i][j]为合并从i到j的最小总花费,那么预处理出前缀和,转移方程为dp[i][j]=max(dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]),且i==j时dp[i][j]=0;核心代码为:
for(int v = 1; v < n; v++)
{
for(int i = 0;i < n-v; i++)
{
int j = i + v;
dp[i][j] = INF;
int tmp = sum[j] - (i > 0 ? sum[i-1]:0);
for(int k = i; k < j; k++)
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j] + tmp);
}
}
环形石子合并问题为洛谷P1880
题目描述
在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分.
输入输出格式
输入格式:数据的第1行试正整数N,1≤N≤100,表示有N堆石子.第2行有N个数,分别表示每堆石子的个数.
输出共2行,第1行为最小得分,第2行为最大得分.
输入输出样例
输入样例#1:
4 4 5 9 4
输出样例#1:
43 54
题解:对于环形的处理典型的方法是将环拆成链,也就是将长度扩大2倍,求链的最佳得分可以通过反向枚举左端点,然后找到当前到右端点的最佳,因为链的长度为原来的两倍,所以应该从2*n-1枚举,每次枚举n的长度,然后从1-n找最后的答案即可。
AC代码:
#include <iostream>
#include <algorithm>
using namespace std;
#define _for(i,a,b) for(int i=a;i<=b;i++)
const int maxn = 507;
const int inf = 0x3f3f3f;
int n,m,a[maxn],sum[maxn],dp_max[maxn][maxn],dp_min[maxn][maxn];
int main(int argc, char const *argv[])
{
cin>>n;
_for(i,1,n)
{
cin>>a[i];
sum[i]=sum[i-1]+a[i];
}
_for(i,n+1,n*2)
{
a[i]=a[i-n];
sum[i]=sum[i-1]+a[i];
}
for(int i=n*2-1;i>0;i--)
{
_for(j,i+1,i+n)
{
dp_min[i][j]=inf;
_for(k,i,j-1)
{
dp_min[i][j]=min(dp_min[i][j],dp_min[i][k]+dp_min[k+1][j]+sum[j]-sum[i-1]);
dp_max[i][j]=max(dp_max[i][j],dp_max[i][k]+dp_max[k+1][j]+sum[j]-sum[i-1]);
}
}
}
int ans_min = inf;
int ans_max = 0;
_for(i,1,n)
{
ans_max=max(dp_max[i][i+n-1],ans_max);
ans_min=min(dp_min[i][i+n-1],ans_min);
}
cout<<ans_min<<endl;
cout<<ans_max<<endl;
return 0;
}
来源:CSDN
作者:sortmin
链接:https://blog.csdn.net/sortmin/article/details/79980323