题解报告:NYOJ #737 石子合并(一)(区间dp)
描述
有N堆石子排成一排,每堆石子有一定的数量。现要将N堆石子并成为一堆。合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这两堆石子的和,经过N-1次合并后成为一堆。求出总的代价最小值。
输入
有多组测试数据,输入到文件结束。
每组测试数据第一行有一个整数n,表示有n堆石子。
接下来的一行有n(0< n <200)个数,分别表示这n堆石子的数目,用空格隔开输出输出总代价的最小值,占单独的一行
样例输入
3 1 2 3 7 13 7 8 16 21 4 18
样例输出
9 239
解题思路:经典区间dp!石子合并问题!定义dp[i][j]表示将区间[i,j]合并后得到的最小代价,易想到相邻先两两合并,再三三合并....,直到将整个区间合并完成,dp[1][n]就是要求的最小代价。
状态转移方程为dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]),其中sum[j]-sum[i-1](预处理前缀和)为将区间[i,j]合并得到的代价,k为断点的枚举。时间复杂度为O(n^3)。
AC代码一(340ms):
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn=205; 4 int n,a[maxn],sum[maxn],dp[maxn][maxn]; 5 int main(){ 6 while(~scanf("%d",&n)){ 7 memset(dp,0x3f,sizeof(dp)); 8 memset(sum,0,sizeof(sum)); 9 for(int i=1;i<=n;++i)scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i],dp[i][i]=0;//初始状态dp[i][i]表示当前每一堆的代价为0 10 for(int len=1;len<=n;++len){//区间长度 11 for(int i=1;i<=n-len;++i){//区间起点 12 int j=i+len;//区间终点 13 for(int k=i;k<j;++k)//断点k把(i,j)分成2堆,dp[i][j]为原来两堆各自的代价和再加上合并的两堆得到的代价之和 14 dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]); 15 } 16 } 17 printf("%d\n",dp[1][n]); 18 } 19 return 0; 20 }
AC代码二(0ms):GarsiaWachs算法,时间复杂度为0(n^2)。
1 #include<iostream> 2 #include<algorithm> 3 #include<cstdio> 4 using namespace std; 5 const int maxn=205; 6 const int inf=0x7fffffff;//2147483647 7 int n,m,t,ans,stone[maxn]; 8 void dfs(int k){ 9 int tmp=stone[k-1]+stone[k]; 10 ans+=tmp;t--; 11 for(int i=k;i<t;++i)stone[i]=stone[i+1];//元素左移,表示删掉了一个元素 12 int j=0;k--; 13 for(j=k;stone[j-1]<tmp;--j)stone[j]=stone[j-1];//元素右移,找到第一个满足条件的j 14 stone[j]=tmp;//将tmp插到j后面 15 while(j>=3&&stone[j-2]<=stone[j]){//继续向前查找是否还有满足条件的情况 16 int d=t-j;//保存当前t离操作点的距离d 17 dfs(j-1);//合并第j-1堆和第j-2堆石子 18 j=t-d;//设置新的操作点j 19 } 20 } 21 int main(){ 22 while(~scanf("%d",&n)){ 23 for(int i=1;i<=n;++i)scanf("%d",&stone[i]); 24 t=2,ans=0;stone[0]=stone[n+1]=inf;//左右边界设置成无穷 25 for(int i=2;i<=n;++i){ 26 stone[t++]=stone[i]; 27 while(t>3&&stone[t-3]<=stone[t-1])dfs(t-2);//表示当前至少有3堆石子,并且满足stone[k-1]<=stone[k+1],其中k=t-2,就合并第t-3和第t-2堆石子 28 } 29 while(t>2)dfs(t-1);//如果剩下的堆数至少为3-1=2堆,则继续合并,直至剩下一堆石子 30 printf("%d\n",ans); 31 } 32 return 0; 33 }