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

题意:给出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);
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值