zoukankan      html  css  js  c++  java
  • DP石子合并问题

    转自:http://www.hnyzsz.net/Article/ShowArticle.asp?ArticleID=735

    【石子合并】
        在一个圆形操场的四周摆放着n 堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的2 堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。
        试设计一个算法,计算出将n堆石子合并成一堆的最小得分和最大得分。 
    【输入文件】
     包含两行,第1 行是正整数n(1<=n<=100),表示有n堆石子。
     第2行有n个数,分别表示每堆石子的个数。 
    【输出文件】
     输出两行。 
     第1 行中的数是最小得分;第2 行中的数是最大得分。 
    【输入样例】
    4
    4 4 5 9
    【输出样例】
    43
    54

    【分析】
        本题初看以为可以使用贪心法解决问题,但是事实上因为有必须相邻两堆才能合并这个条件在,用贪心法就无法保证每次都能取到所有堆中石子数最多的两堆。例如下面这个例子:
        6
        3 4 6 5 4 2
        如果使用贪心法求最小得分,应该是如下的合并步骤:
            第一次合并 3 4 6 5 4 2    2,3合并得分是5
            第二次合并 5 4 6 5 4      5,4合并得分是9
            第三次合并 9 6 5 4        5,4合并得分是9
            第四次合并 9 6 9          9,6合并得分是15
            第五次合并 15 9           15,9合并得分是24
            总得分=5+9+9+15+24=62
        但是如果采用如下合并方法,却可以得到比上面得分更少的方法:
            第一次合并 3 4 6 5 4 2     3,4合并得分是7
            第二次合并 7 6 5 4 2       7,6合并得分是13
            第三次合并 13 5 4 2        4,2合并得分是6
            第四次合并 13 5 6          5,6合并得分是11
            第五次合并 13 11           13,11合并得分是24
            总得分=7+13+6+11+24=61
        由此我们知道本题是不可以使用贪心法求解的,上例中第五次合并石子数分别为13和11的相邻两堆。 这两堆石头分别由最初 的第1,2,3堆(石头数分别为3,4,6)和第4,5,6堆(石头数分别为5,4,2)经4次合并后形成的。于是问题又归结为如何使得这两个子序列的N-2次合并的得分总和最优。为了实现这一目标,我们将第1个序列又一分为二:第1、2堆构成子序列1,第3堆为子序列2。第一次合并子序列1中的两堆,得分7;第二次再将之与子序列2的一堆合并,得分13。显然对于第1个子序列来说,这样的合并方案是最优的。同样,我们将第2个子序列也一分为二;第4堆为子序列1,第5,6堆构成子序列2。第三次合 并子序列2中的2堆,得分6;第四次再将之与子序列1中的一堆合并,得分13。显然对于第二个子序列来说,这样的合并方案也是最优的。由此得出一个结论──6堆石子经过这样的5次合并后,得分的总和最小。 
        动态规划思路:
        阶段i:石子的每一次合并过程,先两两合并,再三三合并,...最后N堆合并
        状态s:每一阶段中各个不同合并方法的石子合并总得分。
        决策:把当前阶段的合并方法细分成前一阶段已计算出的方法,选择其中的最优方案
        具体来说我们应该定义一个数组s[i,j]用来表示合并方法,i表示从编号为i的石头开始合并,j表示从i开始数j堆进行合并,s[i,j]为合并的最优得分。
        对于上面的例子来说,初始阶段就是s[1,1],s[2,1],s[3,1],s[4,1],s[5,1],s[6,1],因为一开始还没有合并,所以这些值应该全部为0。
        第二阶段:两两合并过程如下,其中sum(i,j)表示从i开始数j个数的和
                  s[1,2]=s[1,1]+s[2,1]+sum(1,2)
                  s[2,2]=s[2,1]+s[3,1]+sum(2,2)
                  s[3,2]=s[3,1]+s[4,1]+sum(3,2)
                  s[4,2]=s[4,1]+s[5,1]+sum(4,2)
                  s[5,2]=s[5,1]+s[6,1]+sum(5,2)
                  s[6,2]=s[6,1]+s[1,1]+sum(6,2)
        第三阶段:三三合并可以拆成两两合并,拆分方法有两种,前两个为一组或后两个为一组
             s[1,3]=s[1,2]+s[3,1]+sum(1,3)或s[1,3]=s[1,1]+s[2,2]+sum(1,3),取其最优
             s[2,3]=s[2,2]+s[4,1]+sum(2,3)或s[1,3]=s[2,1]+s[3,2]+sum(2,3),取其最优
                                 .
                                 .
                                 .
        第四阶段:四四合并的拆分方法用三种,同理求出三种分法的得分,取其最优即可。以后第五阶段、第六阶段依次类推,最后在第六阶段中找出最优答案即可。

        由此得到算法框架如下:
        For j←2 to n do    {枚举阶段,从两两合并开始计算}
          For i←1 to n do   {计算当前阶段的n种不同状态的值}
             For k←1 to j-1 do {枚举不同的分段方法}
               begin
                 If i+k>n then t←(i+k) mod n else t←i+k {最后一个连第一个的情况处理}
                 s[i,j]←最优{s[i,k]+s[t,j-k]+sum[1,3]} {sum[i,j]表示从i开始数j个数的和}
               end;

     

    代码:

    Pascal:

     1 var
     2  n:integer;
     3  a:array[1..100] of longint;
     4  s:array[1..100,1..100] of longint;
     5  t:array[0..100,0..100] of longint;
     6  i,j,k,temp,max,min:longint;
     7 begin
     8   assign(input,'shizi.in');
     9   reset(input);
    10   readln(n);
    11   fillchar(t,sizeof(t),0);      {计算和数组}
    12   for i:=1 to n do
    13     read(a[i]);
    14   for i:=1 to n do
    15     for j:=1 to n do
    16       for k:=i to i+j-1 do
    17         begin
    18           if k>n then temp:=k mod n else temp:=k;
    19           t[i,j]:=t[i,j]+a[temp];
    20         end;
    21 {动态规划求最大得分}
    22   fillchar(s,sizeof(s),0);
    23   for j:=2 to n do
    24     for i:=1 to n do
    25       for k:=1 to j-1 do
    26         begin
    27           if i+k>n then temp:=(i+k) mod n else temp:=i+k;  {处理环形问题}
    28           max:=s[i,k]+s[temp,j-k]+t[i,j];
    29           if s[i,j]<max then s[i,j]:=max;
    30         end;
    31   max:=0;        {在最后的阶段状态中找最大得分}
    32   for i:=1 to n do
    33     if max<s[i,n] then max:=s[i,n];
    34 
    35 {动态规划求最小得分}
    36   fillchar(s,sizeof(s),0);
    37   for j:=2 to n do
    38     for i:=1 to n do
    39       begin
    40         min:=maxlongint;
    41         for k:=1 to j-1 do
    42           begin
    43             if i+k>n then temp:=(i+k) mod n else temp:=i+k;  {处理环形问题}
    44             s[i,j]:=s[i,k]+s[temp,j-k]+t[i,j];
    45             if min>s[i,j] then min:=s[i,j];
    46           end;
    47         s[i,j]:=min;
    48       end;
    49   min:=maxlongint;  {在最后的阶段状态中找最小得分}
    50   for i:=1 to n do
    51     if min>s[i,n] then min:=s[i,n];
    52 
    53   writeln(max);
    54   writeln(min);
    55 end.
    View Code

    C:

     1 /*
     2 
     3     DP:
     4         之前没接触过的DP问题,思路不清晰,在参考玩别人思路后有点豁然开朗的感觉。
     5     一般这种类似要计算到区间和的DP问题一般都达到O(n^3)的时间复杂度,其实与上一题的
     6      最小m段和有点相似之处。
     7          思路其实不难,难的是实现,没有一定的积累DP问题的实现是挺难的.求最大和最小
     8      合并思路是一样的,我就只说求最大和并的思路。要求[1,n]间环形的最大合并,即要求
     9      求子区间最大合并,逐步递推子状态...直至得出第i个开始合并n个数的最优解 
    10     
    11     状态转移方程:
    12         dp_max[i][j]=Min(dp_max[i][k]+dp[(i+k+1)%(n+1)][j-k-1]+Sum(i,i+j)) 
    13      
    14 */
    15 #include<stdio.h>
    16 #include<string.h>
    17 #define inf 0x7ffffff 
    18 int sum[105]; //sum[i]表示第1个到第i石子数的和 
    19 int dp_max[105][105],dp_min[105][105]; //dp_max[i][j]表示重第i个开始合并后面j个的最大值 
    20 int n;
    21 int Max(int a,int b)
    22 {
    23     return a>b?a:b;
    24 }
    25 int Min(int a,int b)
    26 {
    27     return a<b?a:b;
    28 }
    29 int Sum(int s,int e) //求s开始后e个数的和 
    30 {
    31     if(s+e>n+1)
    32         return Sum(s,n-s+1)+Sum(1,(s+e)%(n+1));
    33     return sum[s+e-1]-sum[s-1];
    34 } 
    35 void DP(int &max_n,int &min_n)
    36 {
    37     memset(dp_max,0,sizeof(dp_max));
    38     memset(dp_min,0,sizeof(dp_min));
    39     for(int j=2;j<=n;j++){  //连续j个合并 
    40         for(int i=1;i<=n;i++){ //第i个起 
    41             dp_max[i][j]=0;
    42             dp_min[i][j]=inf;
    43             for(int k=1;k<j;k++){
    44                 int temp=(i+k)>n?(i+k)%n:(i+k);
    45                 dp_max[i][j]=Max(dp_max[i][k]+dp_max[temp][j-k]+Sum(i,j),dp_max[i][j]);
    46                 dp_min[i][j]=Min(dp_min[i][k]+dp_min[temp][j-k]+Sum(i,j),dp_min[i][j]);
    47             }
    48         }
    49     }
    50     max_n=dp_max[1][n];
    51     min_n=dp_min[1][n];
    52     for(int i=2;i<=n;i++){
    53         max_n=Max(max_n,dp_max[i][n]);
    54         min_n=Min(min_n,dp_min[i][n]);
    55     }
    56 }
    57 int main(void)
    58 {
    59     int max_n,min_n;
    60     while(scanf("%d",&n)!=EOF)
    61     {
    62         sum[0]=0;
    63         for(int i=1;i<=n;i++){
    64             scanf("%d",&sum[i]);
    65             sum[i]+=sum[i-1];
    66         }
    67         DP(max_n,min_n);
    68         printf("Max=%d
    Min=%d
    ",max_n,min_n);
    69     }
    70     return 0;
    71 }
    72     
    73 /*
    74 
    75 test case:
    76 
    77 5
    78 1 2 3 4 5
    79 
    80 6
    81 3 4 6 5 4 2
    82 
    83 */
    View Code
  • 相关阅读:
    Codeforces 834D The Bakery
    hdu 1394 Minimum Inversion Number
    Codeforces 837E Vasya's Function
    Codeforces 837D Round Subset
    Codeforces 825E Minimal Labels
    Codeforces 437D The Child and Zoo
    Codeforces 822D My pretty girl Noora
    Codeforces 799D Field expansion
    Codeforces 438D The Child and Sequence
    Codeforces Round #427 (Div. 2) Problem D Palindromic characteristics (Codeforces 835D)
  • 原文地址:https://www.cnblogs.com/GO-NO-1/p/3436458.html
Copyright © 2011-2022 走看看