Loading

[HAOI2008] 硬币购物

前言

手贱点进 \(\rm{TJ}\) , 还好啥都没看懂

再次想了一下考试应当怎么考, 并且与平时归起来了
其实焦虑是正常的, 做到自己的最好即可
加油!

思路

你发现这疑似多重背包

\(f_{i, j}\) 表示考虑了前 \(i\) 种硬币, 已经有了 \(j\) 元的可能性

考虑转移

\[f_{i, j} \gets f_{i - 1, j - k \cdot c_i} \]

这个复杂度应该是 \(\mathcal{O} (ns)\) 的, 有些极限, 还有常数

怎么优化这个算法?

你发现又是这种本质相同的问题, 考虑全局的处理

全局处理显然是不考虑 \(d\) 的限制, 你直接处理全局的背包问题, 这是一个 \(\mathcal{O}(s)\) 的完全背包

你发现知道这个的基础上怎么根据 \(d\) 进行计数?

正难则反, 考虑总可能性减去违法的可能性, 压一下违法状态, 带进容斥柿子里计算 (其中 \(f(s)\) 是当前条件的情况下的方案数)

\[ans = \sum_{s = 0}^{2^4 - 1} (-1)^{\lvert s \rvert} f(s) \]

如何计算 \(f(s)\) ?

考虑什么情况下超出了 \(i\) 的限制 : 选择的个数 \(> d_i\)
考虑怎样计算方案数:
你发现一旦选择了 \(d_i + 1\) 个当前硬币, 就一定会不满足 \(i\) 的条件, 余下的任选即可
所以我们利用之前的 \(f\) 数组就可以求出, 具体的, 假设不满足 \(i\) 的条件, 方案数为 \(f_{s - (d_i + 1) \times c_i}\) , 其他的直接计算同理

代码

#include <bits/stdc++.h>

#define ll long long
const int MAXN = 1e5 + 5;
ll f[MAXN], c[10], d[10], s;
inline long long read() {
    char c = getchar();
    long long sum = 0, f = 1;
    while (c < '0' || c > '9') {
        if (c == '-') f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9')
        sum = ((sum << 1) + (sum << 3)) + (c - '0'), c = getchar();
    return sum * f;
}
int main()
{
    for (int i = 1; i <= 4; i++) c[i] = read();
    int n = read();
    f[0] = 1;
    for (int i = 1; i <= 4; i++)
        for (int j = c[i]; j <= 1e5; j++)
            f[j] += f[j - c[i]];

    while (n--) {
        for (int i = 1; i <= 4; i++) d[i] = read();
        s = read();
        ll ans = f[s];
        for (int i = 1; i <= 15; i++) {
            ll k = 0, num = 0;
            for (int j = 0; j < 4; j++) if (i & (1 << j)) num++, k += c[j + 1] * (d[j + 1] + 1);
            
            ll fl = num % 2 ? -1 : 1;
            if (s >= k) ans += fl * f[s - k];
        }
        printf("%lld\n", ans);
    }
    return 0;
}

总结

多个问题有相通的部分, 考虑全局的做一些操作

\(\rm{trick}\) : 正难则反之后用容斥处理答案, 一般可以使用状态压缩的方法

posted @ 2024-12-30 16:03  Yorg  阅读(6)  评论(0)    收藏  举报