石子合并问题

        石子合并问题

题目链接: http://acm.nankai.edu.cn/p1137.html

题目大意: 有若干堆石头排成一个圆。。。给你一个n,表示石头堆数,接下来n个数,每个是对应堆的石头个数,每次操作能合并相邻堆的石头,该次得分就是合并后的石头数,不断进行这样的操作直到只剩下一堆,累计得分,然后输出最大得分和最小得分。

题目思路: 刚拿到这题,一度陷入贪心的陷阱,但事实不是这样,可以举出反例 7、6、5、7、100 ,如果按照贪心的做法:

  1. 6、5 合并得 7、11、7、100  本次得分 11

  2. 7、11 合并得 18、7、100     本次得分 18

  3. 18、7 合并得 25、100          本次得分  25

  最少总分 54 ,剩 25、100

  最优解法:

  1. 7、6 合并   得 13

  2. 5、7 合并   得 12

  3. 12、13 合并 得 25

  最少得分 50 ,剩 25、100

  有了这个样例,所以题目不可以用贪心来解,可以考虑动态规划。。。

  做法代码中解释,有一份简单的比较好理解,一份难点的效率更高。

  

  1 /*
  2   经典DP模型:石子合并问题
  3   链接 :http://acm.nankai.edu.cn/p1137.html
  4 */
  5 
  6 #include<stdio.h>
  7 #include<string.h>
  8 #define N 110
  9 // 对于某种情况,求出该种情况的最大值
 10 int LookMax(int *a,int n)
 11 {
 12   int dp[N][N]; //dp[i][j] : 从第 i 堆开始合并到第 j 堆最大得分
 13   memset(dp,-1,sizeof(dp));  //初始化
 14   for(int i=0;i<n;i++)       //对于每一堆,只合并自身,也就是不进行任何操作,那么得分为0
 15     dp[i][i]=0;
 16   for(int i=0;i<n-1;i++)     //对于每一堆,合并自身和下一堆,最大得分为 :a[i]+a[j]
 17   {
 18     dp[i][i+1]=a[i]+a[i+1];
 19   }
 20   for(int r=2;r<n;r++)       //每一次循环计算 从第 i 堆出发,往右合并 r 堆的最大得分
 21   {
 22     for(int i=0;i<n-r;i++)   //对于不同起点分别计算
 23     {
 24       int j=i+r,sum=0;       //j:终点 sum:从 i 合并到 j 最后一次合并得分 sum,即第i堆到第j堆的石子总数
 25       for(int u=i;u<=j;u++)
 26         sum+=a[u];
 27       dp[i][j]=dp[i][i]+dp[i+1][j]+sum; //每次循环计算的便是 dp[i][j] ,初始化为自身 合并 下一堆到j的最大得分
 28       for(int u=i+1;u<j;u++)            //中间断点,最后合并是由两堆合并组成,那么u 便是枚举 两堆的分界点
 29       {
 30         int tmp=dp[i][u]+dp[u+1][j]+sum;//dp[i][j] 看作 dp[i][u] 和 dp[u+1][j] 的合并
 31         if(tmp>dp[i][j])                //如果这次分法更优,保存更优得分
 32           dp[i][j]=tmp;
 33       }
 34     }
 35   }
 36   return dp[0][n-1];                    //返回最大得分
 37 }
 38 
 39 //寻找最低得分和最高得分类似
 40 int LookMin(int *a,int n)
 41 {
 42   int dp[N][N];
 43   memset(dp,-1,sizeof(dp));
 44   for(int i=0;i<n;i++)
 45     dp[i][i]=0;
 46   for(int i=0;i<n-1;i++)
 47   {
 48     int j=i+1;
 49     dp[i][j]=a[i]+a[j];
 50   }
 51   int mint;
 52   for(int r=2;r<n;r++)
 53   {
 54     for(int i=0;i<n-r;i++)
 55     {
 56       int j=i+r,sum=0;
 57       for(int u=i;u<=j;u++)
 58         sum+=a[u];
 59       dp[i][j]=dp[i+1][j]+sum;
 60       for(int u=i+1;u<j;u++)
 61       {
 62         int tmp=dp[i][u]+dp[u+1][j]+sum;
 63         if(tmp<dp[i][j])
 64           dp[i][j]=tmp;
 65       }
 66     }
 67   }
 68   mint=dp[0][n-1];
 69   return mint;
 70 }
 71 int main()
 72 {
 73   int n;
 74   int a[N];
 75   while(scanf("%d",&n)!=EOF)
 76   {
 77     for(int i=0;i<n;i++)
 78     {
 79       scanf("%d",&a[i]);
 80     }
 81     // 题目中石子是连成一个圈的,为了简化问题,可以分解成n个起点,每种起点对应一种不形成圆的情况
 82     // 对于这 n 种情况,每种情况求最小和最大,最终再得到结果
 83     int mint=LookMin(a,n);
 84     int maxt=LookMax(a,n);
 85     // 循环变化数组
 86     for(int i=1;i<n;i++)
 87     {
 88       int tmp=a[0];
 89       for(int j=1;j<n;j++)
 90       {
 91         a[j-1]=a[j];
 92       }
 93       a[n-1]=tmp;
 94       tmp=LookMin(a,n);
 95       if(tmp<mint) mint=tmp;
 96       tmp=LookMax(a,n);
 97       if(tmp>maxt) maxt=tmp;
 98     }
 99     printf("%d
%d
",mint,maxt);
100   }
101   return 0;
102 }
AC 代码
 1 #include<stdio.h>
 2 #include<string.h>
 3 #define N 110
 4 int max(int a,int b)
 5 {
 6   return a>b?a:b;
 7 }
 8 int min(int a,int b)
 9 {
10   return a>b?b:a;
11 }
12 int main()
13 {
14   int n;
15   int a[N];
16   //这次解法,没有对于n个石子进行n次不同情况的分开判断,而是一次解决,也就是说往右数j堆时会跨越边界
17   int sum[N][N]; // sum[i][j]: 从第 i 堆出发,往右再累计 j 堆的石子数
18   int dp[N][N];  // dp[i][j] : 从第 i 堆出发,往右再合并 j 堆的最大得分
19   int db[N][N];  // db[i][j] : 从第 i 堆出发,往右再合并 j 堆的最小得分
20   while(scanf("%d",&n)!=EOF)
21   {
22     memset(sum,0,sizeof(sum));
23     for(int i=0;i<n;i++)
24     {
25       scanf("%d",&a[i]);
26       sum[i][0]=a[i];          // 从第 i 堆出发,往右再累计0堆,也就是只包括自身,那么石子数为自身石子数
27     }
28     for(int i=0;i<n;i++)       // 注意,这里的总和是可以跨越边界的!不同写法只要能实现功能即可
29       for(int j=1;j<n;j++)
30         sum[i][j]=sum[i][j-1]+a[(i+j)%n];
31     for(int i=0;i<n;i++)       //从第 i 堆出发,往右再合并0堆,那么得分为0
32       dp[i][0]=db[i][0]=0;
33     for(int i=1;i<n;i++)       //往右再合并 i 堆,最多再合并 n-1 堆
34     {
35       for(int j=0;j<n;j++)     //出发点 : j
36       {
37         dp[j][i]=0;            //每次循环计算一个 dp[j][i]
38         db[j][i]=100000000;
39         for(int k=0;k<i;k++)
40         {
41           dp[j][i]=max(dp[j][i],dp[j][k]+dp[(j+k+1)%n][i-k-1]+sum[j][i]);
42           /*
43             dp[j][i] : 根据中间断点不同,可以分为k=0 到 k=i-1
44             k=0 :  自身                     和   后面 i-1 堆        合并
45             k=i-1: 自身和自身后面的i-1堆    和   最后 1 堆          合并
46           */
47           db[j][i]=min(db[j][i],db[j][k]+db[(j+k+1)%n][i-k-1]+sum[j][i]);
48         }
49       }
50     }
51     int maxt=0;
52     int mint=100000000;
53     //对于n个起点 ,寻找最大
54     for(int i=0;i<n;i++)
55     {
56       if(dp[i][n-1]>maxt) maxt=dp[i][n-1];
57       if(db[i][n-1]<mint) mint=db[i][n-1];
58     }
59     printf("%d
%d
",mint,maxt);
60   }
61   return 0;
62 }
AC 代码改进
原文地址:https://www.cnblogs.com/hchlqlz-oj-mrj/p/4898432.html