洛谷 P1450 [HAOI2008]硬币购物

Description

共有 4 4 4 种硬币。面值分别为 c 1 , c 2 , c 3 , c 4 c_1,c_2,c_3,c_4 c1,c2,c3,c4

某人去商店买东西,去了 n n n 次,对于每次购买,他带了 d i d_i di i i i 种硬币,想购买 s s s 的价值的东西。请问每次有多少种付款方法。

Input

输入的第一行是五个整数,分别代表 c 1 , c 2 , c 3 , c 4 , n c_1,c_2,c_3,c_4, n c1,c2,c3,c4,n

接下来 n n n 行,每行有五个整数,描述一次购买,分别代表 d 1 , d 2 , d 3 , d 4 , s d_1, d_2, d_3, d_4,s d1,d2,d3,d4,s

Output

对于每次购买,输出一行一个整数代表答案。

Solution

1.利用容斥定理,先用完全背包计算无限枚硬币的方案数。

实现如下:

// 完全背包模板
    dp[0] = 1;
    for(int i=1; i<=4; i++)
        for(int j=c[i]; j<=100000; j++)
            dp[j] += dp[j-c[i]];

该方案得到的是,肯定包含有四种硬币都不合法的方案数。

2.再由容斥定理,处理不合法的方案。

令四种硬币不合法的方案分别为A、B、C、D,由容斥定理不难得出公式:

P ( A ⋃ B ⋃ C ⋃ D ) = P ( A ) + P ( B ) + P ( C ) + P ( D ) − P ( A ⋃ B ) − P ( A ⋃ C ) − P ( A ⋃ D ) − P ( B ⋃ C ) − P ( B ⋃ D ) − P ( C ⋃ D ) + P ( A ⋃ B ⋃ C ) + P ( A ⋃ B ⋃ D ) + P ( A ⋃ C ⋃ D ) + P ( B ⋃ C ⋃ D ) − P ( A ⋃ B ⋃ C ⋃ D ) P(A\bigcup B\bigcup C\bigcup D)\\=P(A)+P(B)+P(C)+P(D)\\-P(A\bigcup B)-P(A\bigcup C)-P(A\bigcup D)\\-P(B\bigcup C)-P(B\bigcup D)-P(C\bigcup D)\\+P(A\bigcup B\bigcup C)+P(A\bigcup B\bigcup D)\\+P(A\bigcup C\bigcup D)+P(B\bigcup C\bigcup D)\\-P(A\bigcup B\bigcup C\bigcup D) P(ABCD)=P(A)+P(B)+P(C)+P(D)P(AB)P(AC)P(AD)P(BC)P(BD)P(CD)+P(ABC)+P(ABD)+P(ACD)+P(BCD)P(ABCD)

现在的任务就是要在答案中减去不合法的方案,

即对于上述式子中的各个值,对答案贡献的为对应值的相反数。

3.模拟

可以通过二进制的模拟方式,1表示该币种不合法,0表示合法。

显然,需要让该币种不合法,就至少需要使用 d j + 1 d_j+1 dj+1枚,贡献价值 ( d j + 1 ) ∗ c [ j ] (d_j+1)*c[j] (dj+1)c[j] ,多种币种不合法就将该值相加即可。

币种多少对答案的贡献:由2得,奇数为负,偶数为正。

初始答案为完全背包后的 d p [ s ] dp[s] dp[s] 的值。

Code

#include <bits/stdc++.h>

typedef long long ll;
#define MOD 1000000007
#define intmax 2147483647
#define memmax 0x7fffffff

using namespace std;

inline ll read()
{
    
    
	ll x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){
    
    if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){
    
    x=x*10+ch-48;ch=getchar();}
	return x*f;
}


ll n, s;
ll c[5], d[5];

ll dp[100005];

void solve()
{
    
    
    cin >> d[1] >> d[2] >> d[3] >> d[4] >> s;

    ll ans = dp[s];// 无限硬币的答案

    // 完全背包的答案相当于把四个硬币的不合法方案全部计算进去了
    // 模拟四种硬币不合法方案(二进制形式)
    // 保证总有一项为1
    for(int i=1; i<(1<<4); i++)
    {
    
    
        // 判正负
        ll cnt = 1, sum = 0;
        for(int j=1; j<=4; j++)
        {
    
    
            // 检测不合法位
            if(i>>(j-1) & 1)
            {
    
    
                cnt = -cnt;//+-取反
                // 满足不合法至少需要的硬币枚数以及总价值
                sum += (d[j]+1)*c[j];
            }
        }

        // 大于要求的答案,对答案没影响
        if(sum > s)
            continue;
        // 更新答案
        ans += cnt*dp[s-sum];
    }

    cout << ans << endl;

    
}

int main(void)
{
    
    
    
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);


    cin >> c[1] >> c[2] >> c[3] >> c[4] >> n;

    // 完全背包模板
    dp[0] = 1;
    for(int i=1; i<=4; i++)
        for(int j=c[i]; j<=100000; j++)
            dp[j] += dp[j-c[i]];

    for(int i=1; i<=n; i++)
        solve();
    
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_45812280/article/details/121453789