题目描述
在一个圆形操场的四周摆放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
区间DP的版子题,区间DP的方程为f[i][j]=max(f[i][k]+f[k+1][j]+(区间i到j的值))
但这道题稍有不同,所有石子实际上排成了一个环,要区间的DP的话要断环为链,如果依次枚举断点的话,复杂度又要上一个台阶,那怎么办呢
断环为链+倍增
举个例子,如果一个环是1 2 3,在存储时把它倍增,变成1 2 3 1 2 3,这样的话,所有可能的情况都会出现 1 2 3,2 3 1,3 1 2这样就避免了枚举断点的循环
关于区间i-j的值,可以用跑一遍前缀和,查询时就很方便,不过注意要跑1-2*n的前缀和
用记忆化搜索写的,相对于DP,记搜更好写,也更容易排错
用求最小值记搜的来说明
int dfs1(int l,int r) { if(l==r)return f1[l][r]=0;//如果左右断点重合则返回0 if(f1[l][r])return f1[l][r];//算过直接返回 int ans=inf;//求最小值的话搜索初值要设为一个较大值 for(int k=l;k<r;k++)//枚举断点,这里有人可能会问不是只能合并相邻的石子吗,虽然题目里说了只能合并相同的区间,但这样写是等效的,
//合并1-2,2-3的代价和合并1-3的代价是相同的 ans=min(ans,dfs1(l,k)+dfs1(k+1,r)+sum[r]-sum[l-1]);//套方程不解释 return f1[l][r]=ans;//返回 }
最大值同理
完整代码
#include<bits/stdc++.h> using namespace std; const int MAXN=205; const int inf=0x7fffffff; int n,sum[MAXN],a[MAXN],f1[MAXN][MAXN],f2[MAXN][MAXN]; int dfs1(int l,int r) { if(l==r)return f1[l][r]=0; if(f1[l][r])return f1[l][r]; int ans=inf; for(int k=l;k<r;k++) ans=min(ans,dfs1(l,k)+dfs1(k+1,r)+sum[r]-sum[l-1]); return f1[l][r]=ans; } int dfs2(int l,int r) { if(l==r)return f2[l][r]=0; if(f2[l][r])return f2[l][r]; int ans=0; for(int k=l;k<r;k++) ans=max(ans,dfs2(l,k)+dfs2(k+1,r)+sum[r]-sum[l-1]); return f2[l][r]=ans; } int main() { cin>>n; for(int i=1;i<=n;i++) cin>>a[i],a[i+n]=a[i]; for(int i=1;i<=2*n;i++) sum[i]=sum[i-1]+a[i]; dfs1(1,2*n); dfs2(1,2*n); int ans1=inf,ans2=0; for(int i=1;i<=n;i++) { ans1=min(ans1,f1[i][i+n-1]); ans2=max(ans2,f2[i][i+n-1]); } cout<<ans1<<endl<<ans2; return 0; }
参考大佬@ FFF团的题解