洛谷1450:硬币购物

洛谷1450:硬币购物

题目描述:

  • 有四种面值的硬币,面值分别为(c1,c2,c3,c4)
  • 某人去商店买东西,一共去了(T)次,每次带(d_i)(c_i)硬币,买价值为(s_i)的东西,问每次有多少种付款方法。

数据范围:

  • (d_i,s_i leq 1e5)
  • (totleq 1000)

思路:

  • 背包+容斥
  • 对于每一次询问都做一次多重背包,肯定会超时。
  • 暂且先考虑如果每种硬币可以无限次的使用会发生什么情况。
  • 那就是完全背包,可以用(f(i))来表示每种硬币选择任意多次的方案数,有转移方程(f(j)=f(j-c(i)))
  • 先来考虑只有一种硬币的情况。
  • 这里的方案数可能是超过了(d)个的,也就是说结果可能是存在有使用了(d+1,d+2,...)种方案的情况,所以我们需要把他减去。
  • 这里要怎么做呢,先举个例子,比如说我现在有两个区间([2,+infin])([3,+infin])。我想要得到([2,3])区间我只需要将这两个区间相减即可。
  • 那么我们只需要考虑选则任意数量的方案,减去选则(d+1)个硬币的方案。
  • 那么可以知道(ans = f(s)-(f(s)-c*(d+1)))。这样就是选择无限的减去选则(d+1)的。
  • 对于多种硬币,就有(ans=f(s)-sum_{i=1}^{4}f(s-c_i*(d_i+1)))
  • 但是这个式子只是意会用的,他是不能直接累加的,因为可能种类(1)的硬币超出限制,种类(2)的硬币超出限制,而直接累加会导致冗余情况。
  • 所以需要容斥定理,当然这里只有四种硬币,所以可以直接枚举,但是用位运算枚举会方便很多。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 10;
int c[5], d[5], s, T;
ll f[maxn], ans;

int main()
{
    for(int i = 1; i <= 4; i++) scanf("%d", &c[i]);
    f[0] = 1;
    for(int i = 1; i <= 4; i++)
        for(int j = c[i]; j <= maxn-5; j++)
            f[j] += f[j-c[i]];

    scanf("%d", &T);
    while(T--)
    {
        ll ans = 0;
        for(int i = 1; i <= 4; i++)
            scanf("%d", &d[i]);
        scanf("%d", &s);
        ans = f[s];
        for(int i = 1; i < (1<<4); i++)
        {
            ll tmp = 0, num = 0;
            for(int j = 1; j <= 4; j++)
            {
                if((1<<(j-1))&i)
                {
                    num++;
                    tmp += (d[j]+1) * c[j];
                }
            }
            if(tmp > s) continue;
            if(num % 2 == 0) ans += f[s-tmp];
            else ans -= f[s-tmp];
        }
        cout << ans << endl;
    }

    return 0;
}

原文地址:https://www.cnblogs.com/zxytxdy/p/12063893.html