【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);
    }
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章