[lnsyoj729/luoguP1450/HAOI2008]硬币购物

题意

给出一个长度为 \(4\) 的序列 \(c\),给出 \(n\) 个询问,每个询问给出一个长度为 \(4\) 的序列 \(d\) 和 整数 \(s\),要求构造出长度为 \(4\) 的序列 \(t\),使得 \(s = \sum_{i=1}^4 c_i \cdot t_i\),且 \(\forall (x \in \R) \land (1 \le x \le 4), t_x \le d_x)\),求 \(t\) 的方案数

sol

一眼多重背包问题,但无论使用哪种做法都无法在 \(1s\) 内解决,只好思考容斥。
(注:接下来,我们记 \(cnt_x\) 表示满足 \(x = \sum_{i=1}^4 c_i \cdot t_i\)的方案数)
答案显然不是 \(cnt_s\),因为存在序列 \(d\) 限制。考虑减掉对于 \(1\sim 4\) 中的每个数 \(i\),当 \(t_i > d_i\) 时的方案数,即 \(cnt_{s - c_i \cdot (d_i + 1)}\)(因为需要保证 \(t_i > d_i\),因此先选出 \(d_i + 1\)\(c_i\),再正常选择)。
此时多减了两个数多选的情况,由此,即可写出容斥式

\(cnt\) 数组也很好计算,事实上,它就是完全背包一维优化的 \(f\) 数组。

代码

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 100005;

int c[4], d[4];
int n, s;
long long f[N];

int p(int x){
    return 1 - x % 2 * 2;
}

void init(){
    f[0] = 1;
    for (int i = 0; i < 4; i ++ )
        for (int j = c[i]; j <= 100000; j ++ )
            f[j] += f[j - c[i]];
    
}

int main(){
    scanf("%d%d%d%d%d", &c[0], &c[1], &c[2], &c[3], &n);

    init();

    while (n -- ){
        scanf("%d%d%d%d%d", &d[0], &d[1], &d[2], &d[3], &s);

        long long ans = 0;

        for (int s0 = 0; s0 < 16; s0 ++ ){
            int l = 0;
            long long sum = 0;
            for (int u = 0; u < 4; u ++ )
                if ((s0 >> u) & 1) l ++ , sum += (long long) c[u] * (d[u] + 1);
            
            if (s >= sum) ans += p(l) * f[s - sum];
        }

        printf("%lld\n", ans);
    }
}

蒟蒻犯的若至错误

  • init() 和 main() 中的下标起始位置不统一
posted @ 2024-09-28 17:36  是一只小蒟蒻呀  阅读(3)  评论(0编辑  收藏  举报