链接: URAL 1005
题意:给出 n 个石头的重量,现在要求一种方案,把石头分为两堆,这两堆石头的重量差在所有方案中是最小的。
基本思路:如果是熟悉动态规划的人,这道题很容易可以看成是 0-1背包问题,就是价值是每一个石头的重量,然后背包的容量是石头总重量的一半,很容易打的。但是当初第一次做类似的题用的是搜索,可是搜索对于这种0 - 1选择问题很容易爆栈的。不过这道题的 n 实在太小….这么小的数据直接暴力DFS是没什么问题的,可以先计算出石头重量的总和和一半大小,用一个变量 res 保存递归到结束时取到的最大总重量,动态更新,然后从第一个石头开始进行DFS,递归的两个参数为 n 和 cur,n 为当前进行选择的第几个西瓜,cur 为当前已经取到的重量。递归的时候如果取了当前石头后没有超过总重量一半,那么可以取,然后继续向下递归,还有就是不取这块石头的情况进行向下递归。当递归到 n = N+1 时,说明所有石头都已递归过,判断是否应该更新 res,返回。或者如果当前保存的答案已经等于一半重量,那么已经是最优解,退出。最终答案就是 sum – 2*res,体会一下,这就是一个人比另一个多的,即差值。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int N,w[25],sum,res,half;
void DFS(int n,int cur)
{
if(n == N+1)
{
if(cur > res) res = cur;
return;
}
if(res == half)
return;
if(cur + w[n] <= half)
DFS(n+1,cur + w[n]);
DFS(n+1,cur);
}
int main()
{
while(cin>>N)
{
res = 0;sum = 0;
for(int i = 1;i <= N;++i)
{
cin>>w[i];
sum += w[i];
}
half = sum/2;
DFS(1,0);
cout<<(sum - 2*res)<<endl;
}
return 0;
}
优化思路 -- 剪枝:
上面这种方法真的很暴力,若是数据再大可能就爆了。但是对于递归是可以进行剪枝的。在这里我们可以用一个数组 sum, sum[i] 表示从 1 到 i 的石头总重量,然后从 n ,即从最后的石头开始向前递归,边界条件为 n == 0 ,然后再增加一个边界条件就是 cur + sum[n] <= res 时也进行return,即 当前取到的石头重量如果加上剩下没取的石头总重量小于目前取到的最优值时便不再递归,因为已经不可能找到一个对目前最优解来说的更优解。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int w[25],sum[25],res,half;
void DFS(int n,int cur)
{
if(!n)
{
if(cur > res) res = cur;
return;
}
if(res == half || cur + sum[n] <= res) //剪枝
return;
if(cur + w[n] <= half)
DFS(n-1,cur + w[n]);
DFS(n-1,cur);
}
int main()
{
int N;
while(cin>>N)
{
res = 0;sum[0] = 0;
for(int i = 1;i <= N;++i)
{
cin>>w[i];
sum[i] = sum[i-1] + w[i];
}
half = sum[N]/2;
DFS(N,0);
cout<<(sum[N] - 2*res)<<endl;
}
return 0;
}
前面也说了,这道题很容易转换成 0-1 背包问题,每个石头的重量既是价值又是花费的代价,背包容量为石头总重量的一半,接下来就是典型的背包思路解了。但是,要注意该背包的实现必须把空间复杂度降到 O(V)(V代表总重量的一半),不然 O(n*V)的空间复杂度在这道题是会爆内存的。而时间复杂度是O(n*V)。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
#define MAXN 23
#define MAXV 2000005
#define max(a,b) (a)>(b)?(a):(b)
int w[MAXN],d[MAXV];
int main()
{
int n,v,p,sum;
while(scanf("%d",&n)!=EOF)
{
memset(d,0,sizeof(d));
sum = 0;
for(int i = 0;i < n;++i)
{
scanf("%d",&w[i]);
sum += w[i];
}
int V = sum/2;
for(int i = 0; i < n; ++i)
{
v = p = w[i];
for(int j = V;j >= v;--j)
d[j] = max(d[j],d[j - v] + p);
}
printf("%d\n",sum - 2*d[V]);
}
return 0;
}