您的位置:首页 > 其它

【TOJ 3403】Treasure Division 【双向搜索】

2014-08-13 09:43 162 查看
题意:给出n个数字,(n <= 30)将其分成两份,两份的数字数量差不超过1个,求这个两份数字之和的差最小。每个数字都是整数范围内。

思路:n<= 30很明显就是一个双向搜索的题,先将n分成两份,对于其中一份枚举所有情况,那就是2^15,然后对于后半份在枚举所有情况,然后通过二分将两个合起来即可。从而更新答案。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;

ll val[33], sum, ans;

struct R{
ll v;
int c;
bool operator<(const R a)const {
if (c == a.c) return v < a.v;
return c < a.c;
}
}ran[40100];

ll f(ll a) {return a>0?a:-a;}

int find(int l, int r, ll k, ll tm) {
if (l == -1 || r == -1) return -1;
int mid, tl, tr;
tl = l, tr = r;
while (l < r) {
mid = l+r>>1;
if (ran[mid].v < k) l = mid+1;
else r = mid;
}
ans = min(ans, f(sum-2*(ran[l].v+tm)));
if (l > tl) ans = min(ans, f(sum-2*(ran[l-1].v+tm)));
if (l < tr) ans = min(ans, f(sum-2*(ran[l+1].v+tm)));
}

int l[33], r[33];

int main() {
int n, i, j, m;
while (~scanf("%d", &n)) {
sum = 0;
for (i = 0;i < n;i++) {
scanf("%lld", &val[i]);
sum += val[i];
}
int st = (1<<(n/2)), c;
m = 0;
for (i = 0;i < st;i++) {
ll tm = 0;
c = 0;
for (j = 0;j < n/2;j++) {
if (i&(1<<j)) tm += val[j], c++;
}
ran[m].v = tm, ran[m++].c = c;
}
sort(ran, ran+m);
memset(l, -1, sizeof(l));
memset(r, -1, sizeof(r));
for (i = 0;i < m;i++) {
if (i == 0 || ran[i].c != ran[i-1].c) {
l[ran[i].c] = i;
}
if (i == m-1 || ran[i].c != ran[i+1].c) {
r[ran[i].c] = i;
}
}
ans = sum;
st = (1<<(n-n/2));
for (i = 0;i < st;i++) {
ll tm = 0;
c = 0;
for (j = n/2;j < n;j++) {
if (i&(1<<(j-n/2))) tm += val[j], c++;
}
if (c > n/2+1) continue;
if (n&1) {
if (c > n/2) {
ans = min(ans, f(sum-2*tm));
continue;
}
j = find(l[n/2-c], r[n/2-c], sum/2-tm, tm);
j = find(l[n/2-c+1], r[n/2-c+1], sum/2-tm, tm);
}else {
if (c > n/2) continue;
j = find(l[n/2-c], r[n/2-c], sum/2-tm, tm);
}
}
printf("%lld\n", ans);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: