题解报告: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 }

 

posted @ 2018-09-11 00:01  霜雪千年  阅读(262)  评论(0编辑  收藏  举报