超大背包问题 - 折半搜索

超大背包问题:有n个重量和价值分别为w[i]和v[i]的物品,从这些物品中挑选总重量不超过W的物品,求所有挑选方案中价值总和的最大值。其中,1 ≤ n ≤ 40, 1 ≤ w[i], v[i] ≤ 10^15, 1 ≤ W ≤ 10^15.

按照普通的DP 思路显然是无法求解的, 背包的体积太大了, 那么就要换种思考的方向,观察物品的数量只有 40 个,普通的枚举话 2 ^ 40,肯定是超时,那么如何是分成两堆呢 ? 在按照字典序去枚举,不就没问题了吗 ?

int n, W;
int v[50], w[50];

struct node
{
    int ww, vv;
    node(int _w = 0, int _v = 0):ww(_w), vv(_v){}
    
}pre[1<<25];

bool cmp(node a, node b){
    if (a.vv == b.vv) return a.ww > b.ww;
    else return a.vv < b.vv;
}
int ans;

int fun(int l, int r, int key){
    while(l <= r){
        int mid = (l + r) >> 1;
        if (pre[mid].vv == key) return pre[mid].ww;
        else if (pre[mid].vv < key) l = mid + 1;
        else r = mid - 1;
    }
    return pre[r].ww;
}

void solve(){
    int n2 = n / 2;
    for(int i = 0; i < 1 << n2; i++){
        int sv = 0, sw = 0;
        for(int j = 0; j < n2; j++){
            if ((i >> j) & 1){
                sw += w[j];
                sv += v[j];
            }
        }
        pre[i] = node(sw, sv);    
    }
    sort(pre, pre+ (1 << n2), cmp);
    int m = 0;
    for(int i = 1; i < 1<<n2; i++){
        if (pre[m].ww < pre[i].ww){
            pre[++m] = pre[i];
        }
    }
    ans = 0;
    for(int i = 0; i < 1<<(n-n2); i++){
        int sv = 0, sw = 0;
        for(int j = 0; j < (n-n2); j++){
            if ((i >> j) & 1){
                sw += w[n2+j];
                sv += v[n2+j];
            }
        }
        if (sv <= W){         
            int f = fun(0, m, W-sv);
            ans = max(ans, f + sw); 
        }
    }
}

int main() {
    //freopen("in.txt", "r", stdin);
    //freopen("out.txt", "w", sttout);
    
    while(~scanf("%d%d", &n, &W)){
        for(int i = 0; i < n; i++){
            scanf("%d%d", &v[i], &w[i]);
        }
        solve();
        printf("%d\n", ans);
    }
    return 0;
}
/*
4 5
2 3
1 2
3 4
2 2
*/

 

posted @ 2017-11-19 21:03  楼主好菜啊  阅读(557)  评论(0编辑  收藏  举报