传送门:http://acm.timus.ru/problem.aspx?space=1&num=1005
参考:https://www.cnblogs.com/yinzm/p/6629222.html
题意:
给出许多石子堆,问能分成怎样的两堆,使得两堆差距最小;
思路:我看到题解后才意识到以前可能写过类似的,竟然是01背包;
看完题目之后,我们可以了解一下数据的规模,最多就20个石头,所以我们可以列出我们能够解题的一些方法:
- 直接深度优先搜索,暴力得到每一种划分方法
- 二进制枚举,这也是一种暴力求解方法
- 将题目转化为01背包问题,动态规划求解
结合题目时间限制,我们最终选择第三种方法。所以现在来重点讲解下,这个题目需要怎么转化为01背包问题的。
首先,将石头划分成AB两堆,那么我们相当于从一堆石头里面挑选出若干个石头出来。对于每一块石头都有挑选或者不挑选两种选择。这就有点像01背包问题了,但是目前还不确定如何用01背包来求解。题目要求我们求解两堆石头的最小差,如果石头是可以掰开的,那么最理想的状态当然是两堆石头的重量都是原始那堆石头重量的一半avg,但是现实不是那么完美的,我们不能将石头掰开揉碎,而且很有可能最终的最好结果确实是一堆重一堆轻。
假设最优的结果是一堆石头重量为A,另外一堆是B,那么AB两者可能是其中一个大于等于avg,另外一个是小于等于avg。我们现在需要使|A-B|
最小,那么最好是AB越靠近avg越好,这就相当于我们拿一个最大承重为avg的袋子去装石头,最多能装多少重量的石头。这就是一个01背包问题了。接下来我们只要求出这个背包问题的解,那么最后的差也就能够计算出来了。
#include <iostream> #include <algorithm> using namespace std; const int maxn = 1e6+5; int n; int a[22]; int dp[maxn]; int ab(int x) { return x >0? x:-x; } int main() { scanf("%d",&n); int m=0; for(int i=1;i<=n;i++) scanf("%d",&a[i]), m+=a[i]; int ave = m/2; //cout<<m<<endl; for(int i=1;i<=n;i++) { for(int j=ave;j>=0;j--) { if(j>=a[i]) { dp[j]=max(dp[j],dp[j-a[i]]+a[i]); } } } //cout<<dp[ave]<<endl; int ans = ab(dp[ave]-(m-dp[ave])); printf("%d ",ans); //sort(a+1,a+1+n); return 0; }