动态规划中的石子归并问题

一.有N堆石子,每堆的重量是w[i],可以任意选两堆合并,每次合并的花费为w[i]+w[j],问把所有石子合并成为一堆后的最小花费是多少。
因为是可以任意合并,所以每次合并的时候选最小的两堆合并,贪心即可。

 

二.有N堆石子,每堆的重量是a[i],排成一条直线,每次只能合并相邻的两堆,直到合成一堆为止,问最后的最小花费是多少。
分析:因为规定了只能合并相邻的两堆,显然不能使用贪心法。
分成子问题来考虑,定义dp[i][j]表示从第i的石子合并到第j个石子的最小花费,那么dp[1][N]就是问题的解。
可以推出dp[i][j] = min(dp[i][k]+dp[k+1][j]) k∈(i,j)
初始时dp[i][j] = INF(i!=j) dp[i][i] = INF

问题链接

 1 #include <iostream>
 2 #include <cstring>
 3 #include <cstdio>
 4 #include <string>
 5 #include <algorithm>
 6 using namespace std;
 7 int T, n;
 8 int a[210], dp[210][210], sum[210], s[210][210];
 9 //这里是数据量比较小
10 int INF = 99999999;
11 int main(){
12     while(scanf("%d", &n) != EOF){
13             memset(sum, 0, sizeof(sum));
14             for(int i = 1; i <= n; i++){
15                 cin>>a[i];
16                 sum[i] = sum[i-1] + a[i];
17             }
18             for(int i = 1; i <= n; i++){
19                 for(int j = 1; j <= n; j++) 
20                     dp[i][j] = INF;
21             }
22             for(int i = 1; i <= n; i++) dp[i][i] = 0;
23             
24             for(int len = 2; len <= n; len++){      //表示归并的长度 
25                 for(int i = 1; i <= n-len+1; i++){  //归并的第一位 
26                     int j = i+len-1;                //归并的最后一位 
27                     for(int k = i; k < j; k++){
28                         dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j] + sum[j] - sum[i-1]);
29                     }
30                 }
31             }
32             cout<<dp[1][n]<<endl;
33     }
34     return 0;
35 }

这样复杂度是O(N^3)

可以利用四边形不等式优化到O(N^2)

四边形不等式: 如果对于任意的a≤b≤c≤d,有

 m[a,c] + m[b,d] <= m[a,d] + m[b,c]

那么m[i,j]满足四边形不等式。

对于转移方程形如下形式的动态规划问题:m[i,j] = opt{m[i,k] + m[k,j] + w[i,j]} (其中w[i,j]是m的附属量)

首先证明w满足四边形不等式,然后再证明m满足四边形不等式。最后证明s[i,j-1] ≤ s[i,j] ≤ s[i+1,j]这条性质来优化转移变量k的枚举量。

 1 #include <iostream>
 2 #include <cstring>
 3 #include <cstdio>
 4 #include <string>
 5 #include <algorithm>
 6 using namespace std;
 7 int T, n;
 8 int a[210], dp[210][210], sum[210], s[210][210];
 9 //这里是数据量比较小
10 //时间复杂度是N^3 
11 int INF = 99999999;
12 int main(){
13     while(scanf("%d", &n) != EOF){
14             memset(sum, 0, sizeof(sum));
15             memset(s,0,sizeof(s));
16             for(int i = 1; i <= n; i++){
17                 cin>>a[i];
18                 s[i][i] = i;  //只有一个的时候k当然等于i和j 
19                 sum[i] = sum[i-1] + a[i];
20             }
21             for(int i = 1; i <= n; i++){
22                 for(int j = 1; j <= n; j++) 
23                     dp[i][j] = INF;
24             }
25             for(int i = 1; i <= n; i++) dp[i][i] = 0;
26             
27             for(int len = 2; len <= n; len++){          //表示归并的长度 
28                 for(int i = 1; i <= n-len+1; i++){  //归并的第一位 
29                     int j = i+len-1;                //归并的最后一位 
30                     for(int k = s[i][j-1]; k <= s[i+1][j]; k++){
31                         if(dp[i][j] > dp[i][k] + dp[k+1][j] + sum[j] - sum[i-1]){
32                             dp[i][j] = dp[i][k] + dp[k+1][j] + sum[j] - sum[i-1];
33                             s[i][j] = k;
34                         }
35                     }
36                      
37                 }
38             }
39             cout<<dp[1][n]<<endl;
40     }
41     return 0;
42 }

三.环形石子归并问题

参考:http://www.cnblogs.com/SCAU_que/articles/1893979.html

在一个圆形操场的四周摆放着n 堆石子。现要将石子有次序地合并成一堆。
规定每次只能选相邻的2 堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。
试设计一个算法,计算出将n堆石子合并成一堆的最小得分和最大得分。

求最小的分和最大得分类似,下面只求最小得分。

分析:这种情况下可以看成两个直线的石子进行合并,但是需要修改一下dp的定义

dp[i][j]表示的是从i堆石子开始合并j堆的得分。

这样状态转移方程就是dp[i][j] = min(dp[i][k] + dp[(i+k-1)%n+1][j-k] + sum[i][j])  这里取余的时候需要注意一下

sum[i][j]也表示从第i堆石子加后面j堆石子的总量。

网上没有找到题目,以上面的题目变成环形的情况写了一个代码

 1 #include <iostream>
 2 #include <cstring>
 3 #include <cstdio>
 4 #include <string>
 5 #include <algorithm>
 6 using namespace std;
 7 #define maxn 210
 8 #define INF 99999999
 9 int a[maxn], dp[maxn][maxn], sum[maxn][maxn];
10 int T, n;
11 int main(){
12     scanf("%d", &T);
13     while(T--){
14         scanf("%d", &n);
15         for(int i = 1;i  <= n; i++){
16             scanf("%d", &a[i]);
17         }
18         memset(sum, 0, sizeof(sum));
19         for(int i = 1; i <= n; i++){
20             for(int j = 1; j <= n; j++){
21                 sum[i][j] = sum[i][j-1] + a[(i+j-2)%n+1];
22             }
23         }
24         for(int i = 0; i <= n; i++){
25             for(int j = 0; j <= n; j++){
26                 dp[i][j] = INF;
27             }
28         }
29         for(int i = 1; i <= n; i++) dp[i][1] = 0;
30         for(int j = 2; j <= n; j++){
31             for(int i = 1; i <= n; i++){
32                 for(int k = 1; k < j; k++){
33                     dp[i][j] = min(dp[i][j], dp[i][k] + dp[(i+k-1)%n+1][j-k] + sum[i][j]);
34                 }
35             }
36         }
37         int ans = INF;
38         for(int i = 1; i <= n; i++){
39             if(dp[i][n] <= ans) ans = dp[i][n];
40         }
41         printf("%d\n", ans);
42     }
43     return 0;
44 }

http://poj.org/problem?id=1179 这道题数据量很大,还没有写,标记一下。

posted @ 2015-03-17 16:36  下周LGD该赢了吧  阅读(1210)  评论(0编辑  收藏  举报