石子合并问题
石子合并问题
题目链接: 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\n%d\n",mint,maxt); 100 } 101 return 0; 102 }
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\n%d\n",mint,maxt); 60 } 61 return 0; 62 }