跳转至

BZOJ4800 世界冰球锦标赛

题面

题目描述

n 个物品,m 块钱,给定每个物品的价格,求买物品的方案数。

输入格式

第一行两个数 n,m 代表物品数量及钱数

第二行 n 个数,代表每个物品的价格

n\leq 40,m\leq 10^{18}

输出格式

一行一个数表示购买的方案数

(想怎么买就怎么买,当然不买也算一种)

题解

meet_in_the_middle的模板题,直接暴力就行。

将物品分成左边和右边两半先将左边一半(最多有 20 个数)的所有方案状压起来存进数组中,设 v[S] 为状态为 S 时所有权值和,将数组排序后,枚举右边一半的所有方案,用 lower\_bound 求出对于左边数组有多少个方案满足和当前方案权值总和小于 m

有几个优化方法(我也不确定到底有没有实际作用):

  • 使用 lowbit 枚举某一个方案的所有为 1 的位。
  • 预处理出 (1 << 0) - (1 << 20)log 是多少。
  • lower\_bound 快速求左边对应有多少个方案满足。
  • 快读

程序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#define MAX_N 1050007
typedef long long ll;

int N;
ll M;
ll a[47], v[MAX_N], res;
int Log[MAX_N];

inline ll read () {
    ll x = 0;
    char c = getchar();
    while (c < '0' || '9' < c)
        c = getchar();
    while ('0' <= c && c <= '9')
        x = x * 10 + c - '0', c = getchar();
    return x;
}

int main () {
    scanf("%d", &N);
    M = read();
    for (int i = 1;i <= N; ++i)
        a[i] = read();
    int mid = 1 + N >> 1;
    int t = (1 << mid) - 1;
    for (int i = 0;i <= 20; ++i)
        Log[1 << i] = i;
    for (int i = 0;i <= t; ++i) {
        v[i] = 0;
        for (int s = i; s > 0;) {
            v[i] += a[Log[(s & (-s))] + 1];
            if (v[i] > M) break;
            s &= (~(s & -s));
        } 
    }
    std::sort(v, v + t + 1);
    int t2 = (1 << N - mid) - 1;
    ll val = 0;
    for (int i = 0;i <= t2; ++i) {
        val = 0;
        for (int s = i; s;) {
            val += a[Log[(s & (-s))] + mid + 1];
            if (val > M) break;
            s &= (~(s & (-s)));
        }
        if (val > M) continue;
        res += std::upper_bound(v, v + t + 1, M - val) - v;
    }
    printf("%lld\n", res);
    return 0;
}