题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3017
题目大意:
有 \(n(\le 30)\) 块硬币(\(n\) 可能是奇数),每块硬币都有一个币值。要求将 \(n\) 块金币分成两堆,使得两堆硬币币值和的差尽可能地小。输出这个最小的差。
解题思路:
暴力搜索的时间复杂度为 \(O(2^{30})\),会超时,所以考虑使用折半搜索,时间复杂度降为 \(O(2 \cdot 15 \cdot 2^{15})\)。
示例代码:
#include <bits/stdc++.h>
using namespace std;
int n;
long long v[31], ans;
set<long long> st[16];
void dfs1(int id, int cnt, long long tmp) {
if (id > n/2) {
st[cnt].insert(tmp);
return;
}
dfs1(id+1, cnt, tmp-v[id]);
dfs1(id+1, cnt+1, tmp+v[id]);
}
void dfs2(int id, int cnt, long long tmp) {
if (id > n) {
int p = n/2 - max(0, n/2 - cnt);
assert(p >= 0 && p <= n/2);
set<long long>::iterator it = st[p].lower_bound(tmp);
if (it != st[p].end()) {
int val = *it;
ans = min(ans, abs(tmp - val));
}
if (it != st[p].begin()) {
it --;
int val = *it;
ans = min(ans, abs(tmp - val));
}
return;
}
dfs2(id+1, cnt, tmp-v[id]);
dfs2(id+1, cnt+1, tmp+v[id]);
}
int main() {
while (~scanf("%d", &n)) {
for (int i = 0; i <= n/2; i ++) st[i].clear();
ans = (1LL<<60);
for (int i = 1; i <= n; i ++) scanf("%lld", v+i);
dfs1(1, 0, 0);
dfs2(n/2+1, 0, 0);
printf("%lld\n", ans);
}
return 0;
}