超大背包问题 - 折半搜索
超大背包问题:有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 */
东北日出西边雨 道是无情却有情