石子合并
题目描述
在一个圆形操场的四周摆放 N 堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
试设计出一个算法,计算出将 N 堆石子合并成 1 堆的最小得分和最大得分。
这是个圆形的操场QAQ,那就2*n 排成一排就可以了;
集合表示:dp[ i ][ j ] 表示区间 [ i , j ] 内的石子合并方式的集合,其属性为得分的最小或最大值;
集合划分:根据区间[ i,j ] 的最后一次合并位置 k 来划分:
dp[ i ][ k ] :合并 [ i,k] 的最小得分
dp[ k+1][ j ] :合并[k+1, j] 的最小得分
状态转移方程:dp[i][j] = min( dp[i][k] + dp[k+1][ j ] + s[ j ] - s[i-1]) ;
边界处理:dp[i][j] = INF;
特别的,由于我们要由小区间的状态转移到大区间,也就是说我们要确保在[i,j] 内的区间都已经有了合理状态(类似递归的回溯),所以我们通过从小到大枚举区间长度来实现;
#include<iostream>
#include<cstring>
using namespace std;
const int N = 310;
int dp[N][N];
int s[N];
int main()
{
int n,t;
cin>>n;
for(int i=1;i<=n;i++){
cin>>t;
s[i] = s[i-1] + t;
s[n+i] = t;
}
for(int i=n+1;i<=2*n;i++) s[i] += s[i-1];
for(int len=2;len<=2*n;len++){ //枚举所有长度为len的区间
for(int i=1;i+len-1<=2*n;i++){ //所有长度为len的左端点
int l = i,r = i+len-1; //计算右端点
dp[l][r] = 1e9; //边界
for(int k=l;k<r;k++)
dp[l][r] = min(dp[l][r],dp[l][k]+dp[k+1][r]+s[r]-s[l-1]);
}
}
int minn=1e9;
for(int i=1;i<=n;i++) minn = min(minn,dp[i][n+i-1]);
memset(dp,0,sizeof dp);
for(int len=2;len<=2*n;len++){
for(int i=1;i+len-1<=2*n;i++){
int l = i,r = i+len-1;
for(int k=l;k<r;k++)
dp[l][r] = max(dp[l][r],dp[l][k]+dp[k+1][r]+s[r]-s[l-1]);
}
}
int maxx=0;
for(int i=1;i<=n;i++) maxx = max(maxx,dp[i][n+i-1]);
cout<<minn<<"\n"<<maxx<<endl;
return 0;
}