LuoguP1880[NOI1995]石子合并
Description:
在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆。规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分。
区间DP,状态转移方程dp[l][r] = max(dp[l][r],dp[l][k] + dp[k+1][r] + sum[r] - sum[l-1]) 其中,k为区间中间点,sum[] 维护前缀和
区间动归状态转移方程及一般动规过程:
for len:=1 to n-1 do //区间长度
for l:=1 to n-len do //区间起点
for k:=l to l+len-1 do //区间中任意点
dp[r,l+len]:=max{dp[l,k] + dp[k+1,l+len] + a[l,k] + a[k+1,i+len]
区间长度len必须要放到第一层循环,来保证方程中状态dp[l,k]、dp[k+1,l+k]值在dp[l,l+k]之前就已计算出来。
一开始不明白为什么要用堆数作为阶段,写了个类似于Floyd的DP
for(int k = l;k < r;++k)
{
for(int l = 1;l < n << 1;++l){
for(int r = l + 1;r <= n << 1;++r){
dp1[l][r] = min(dp1[l][r],dp1[l][k] + dp1[k + 1][r] + sum[r] - sum[l-1]);
dp2[l][r] = max(dp2[l][r],dp1[l][k] + dp2[k + 1][r] + sum[r] - sum[l-1]);
}
}
}
但是编译没通过,因为k循环不好赋值,难以实现,因此还是枚举区间长度好了。
https://www.cnblogs.com/Zforw/p/10617652.html
Code
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
#include<algorithm>
#include<cmath>
#define N 110
#define ll long long
using namespace std;
ll dp2[N << 1][N << 1],dp1[N << 1][N << 1],n,ans1,ans2;
ll sum[N << 1],a[N << 1];
int main() {
scanf("%lld",&n);
memset(dp1,0x3f,sizeof(dp1));
for(int i = 1; i <= n; i++) {
scanf("%lld",&a[i]);
a[i + n] = a[i];
}
for(int i = 1;i <= n << 1;++i){
sum[i] = sum[i-1] + a[i];
dp2[i][i] = dp1[i][i] = 0;
}
for(int i = 1; i <= n;++i)
dp2[i][i] = dp1[i][i] = 0;
// 以合并的堆数为阶段
for(int len = 2; len <= n;++len) {
for(int l = 1; l <= (n << 1 | 1) - len;++l) {
int r = len + l - 1;
for(int k = l; k < r;++k) {
dp1[l][r] = min(dp1[l][r],dp1[l][k] + dp1[k + 1][r] + sum[r] - sum[l-1]);
dp2[l][r] = max(dp2[l][r],dp2[l][k] + dp2[k + 1][r] + sum[r] - sum[l-1]);
}
}
}
ans1 = 0x3f3f3f3f;
for(int i = 1;i <= n;++i) ans1 = min(ans1,dp1[i][i + n - 1]);
for(int i = 1;i <= n;++i) ans2 = max(ans2,dp2[i][i + n - 1]);
printf("%lld\n%lld",ans1,ans2);
return 0;
}
岂能尽如人意,但求无愧我心