2019-08-19  2024-09-15    996 字  2 分钟

BZOJ-1042 [HAOI2008]硬币购物

背包 DP + 容斥原理

如果用背包做的话复杂度是 $math_inline$O(4nS)$math_inline$ ,无法承受。这道题最明显的特点就是硬币一共只有四种。抽象模型,其实就是让我们求方程 $math_inline$\sum_{i=1}^4C_ix_i=S,x_i\leq D_i$math_inline$ 的非负整数解的个数。

采用同样的容斥方式, $math_inline$x_i$math_inline$ 的属性为 $math_inline$x_i\leq D_i$math_inline$ . 套用容斥原理的公式,最后我们要求解

$math_inline$ \sum_{i=1}^4C_ix_i=S-\sum_{i=1}^kC_{a_i}(D_{a_i}+1) $math_inline$

也就是无限背包问题。这个问题可以预处理,算上询问,总复杂度 $math_inline$O(4S+2^4n)$math_inline$ .

首先,求出不限制使用次数,购买价值为 $math_inline$c$math_inline$ 时的方案数,设它为 $math_inline$f(c)$math_inline$ 。

对于每次询问,我们可以不限制使用次数,购买价值 $math_inline$s_i$math_inline$ 的方案数,减去任意一种硬币超过限制的方案数。

任意一种硬币超过限制的方案数可以用容斥原理求出,即:每一种硬币超过限制的方案数之和-每两种硬币超过限制的方案数之和+每三种硬币超过限制的方案数之和-四种硬币全部超过限制的方案数。

考虑如何求出第 $math_inline$i$math_inline$ 种硬币超过限制的方案数:我们至少要使用 $math_inline$d_i+1$math_inline$ 个第 $math_inline$i$math_inline$ 种硬币,剩余的 $math_inline$s-(d_i+1)\times c_i$math_inline$ 元可以任意选择,即 $math_inline$f(s-(d_i+1)\times c_i)$math_inline$

Code 1
#include <bits/stdc++.h>

using namespace std;
const int S = 1e5 + 5;
int c[5], d[5], n, s;
long long f[S];

int main() {

    cin >> c[1] >> c[2] >> c[3] >> c[4] >> n;
    f[0] = 1;
    for (int j = 1; j <= 4; j++)
        for (int i = 1; i < S; i++)
            if (i >= c[j]) f[i] += f[i - c[j]];
    for (int i = 1; i <= n; i++) {
        cin >> d[1] >> d[2] >> d[3] >> d[4] >> s;
        long long ans = 0;
        for (int j = 1; j < 16; j++) {
            int m = s, bit = 0;
            for (int k = 1; k <= 4; k++)
                if ((j >> (k - 1)) & 1) m -= (d[k] + 1) * c[k], bit++;
            if (m >= 0) ans += (bit % 2 * 2 - 1) * f[m];
        }
        cout << f[s] - ans << endl;
    }
    return 0;
}
Code 2
#include <bits/stdc++.h>

using namespace std;
const int MAXM = 1e5;

int main() {
    int c[5], n;
//    ofstream cout; cout.open("out");
    cin >> c[1] >> c[2] >> c[3] >> c[4] >> n;
    static long long f[5][MAXM + 1];
    f[0][0] = 1;
    for (int i = 1; i <= 4; ++i) { // Total number of programs
        for (int j = 0; j <= MAXM; ++j) {
            if (j < c[i]) f[i][j] = f[i - 1][j];
            else f[i][j] = f[i - 1][j] + f[i][j - c[i]];
        }
    }
    /*
    cout << "COIN #" << setw(2) << c[0] << " ";
    for(int j = 0; j <= 50; ++j) cout << setw(5) << left << j; cout << endl;
    for(int i = 0; i <= 4; ++i){
        cout << "COIN #" << setw(2) << c[i] << " ";
        for(int j = 0; j <= 50; ++j){
            cout << setw(5) << left  << f[i][j];
        }cout << endl;
    }
     */

    while (n--) {
        int d[5], m;
        cin >> d[1] >> d[2] >> d[3] >> d[4] >> m;

        long long ans = f[4][m]; // Total number of programs in m

        // 每一枚硬币超过限制 -A-B-C-D
        for (int i = 1; i <= 4; ++i) {
            if (m - (d[i] + 1) * c[i] >= 0) ans -= f[4][m - (d[i] + 1) * c[i]];
        }

        // 每二枚硬币超过限制 +AB+AC+AD+BC+BD+CD
        for (int i = 1; i <= 4; ++i) {
            for (int j = i + 1; j <= 4; ++j) {
                if (m - (d[i] + 1) * c[i] - (d[j] + 1) * c[j] >= 0)
                    ans += f[4][m - (d[i] + 1) * c[i] - (d[j] + 1) * c[j]];
            }
        }

        // 每三枚硬币全部超过限制 -ABC-ABD-ACD-BCD
        for (int i = 1; i <= 4; ++i) {
            for (int j = i + 1; j <= 4; ++j) {
                for (int k = j + 1; k <= 4; ++k) {
                    if (m - (d[i] + 1) * c[i] - (d[j] + 1) * c[j] - (d[k] + 1) * c[k] >= 0)
                        ans -= f[4][m - (d[i] + 1) * c[i] - (d[j] + 1) * c[j] - (d[k] + 1) * c[k]];
                }
            }
        }

        // 每四枚硬币全部超过限制 +ABCD
        if (m - ((d[1] + 1) * c[1]) - ((d[2] + 1) * c[2]) - ((d[3] + 1) * c[3]) - ((d[4] + 1) * c[4]) >= 0)
            ans += f[4][m - (d[1] + 1) * c[1] - (d[2] + 1) * c[2] - (d[3] + 1) * c[3] - (d[4] + 1) * c[4]];

        cout << ans << endl;
    }
}