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


    一.有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
    ", ans);
    42     }
    43     return 0;
    44 }

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

  • 相关阅读:
    浏览器切换窗口事件
    icheck的使用
    datetimepicker —— 日期选择控件
    apktool
    生成证书
    Jadx
    js call、apply和bind
    1号台风来了
    Linux内核编译和测试
    华中科技大学电子地图
  • 原文地址:https://www.cnblogs.com/titicia/p/4344765.html
Copyright © 2020-2023  润新知