P1880 [NOI1995]石子合并 (区间DP)

石子合并

题目描述
在一个圆形操场的四周摆放 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
#include
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;
}

你可能感兴趣的:(DP)