bzoj1042_递推加容斥原理

2 篇文章 0 订阅
2 篇文章 0 订阅

又一道容斥原理的题目。

最开始看的时候只会打暴力, 想到了正常的dp, 实在想不出来了, 就厚着脸皮去看题解。 最近老是在做这一类的事情, 看完题解之后又感觉很简单... 容斥原理的题目果然还是不熟悉啊。 当然了, 看题解的时候比较喜欢那些有详细证明的, 因为没有证明自己想会很累(我好懒啊)。

这道题的关键点自然还是容斥原理, 对于四种硬币的使用情况, 如果我们记有i种使用过度的方案数为g[i]的话, 那么答案ans = g[4] - g[3] + g[2] - g[1], 这是由容斥原理直接得到的。 剩下的事情就是求g[i]。 其实这是一个抽象的过程, 我们最后用递归来实现就行了, 而不是去求g[]数组。 不妨设第一类硬币使用过度, 即使用量超过了lim[1], 那么剩下的可任取, 也就是说这个时候的情况数与没有限制地取到sum-(c[1]*(lim[1]+1))的情况数是相同的, 因为一旦使用的量多于d[1]后其他情况就无所谓了, 对于其他类别的硬币有同样的情况。 至于没有限制时的情况, 我想用递推很快就可以算出来了吧。

构造一个搜索框架, 记录先前有几种硬币使用过度st, 由最开始ans的表达式我们可以知道st为奇数时, ans需减掉这个数, 反则加上这个数, 因此在进行了越界的决策后, 我们要让st变量自取反, 这样就得到了代码中的dfs函数。

#include <cstdio>
#define N 100000 + 10

using namespace std;

typedef long long LL;
int m, t, c[4],  lim[4];
LL f[N], ans;
void first()
{
    f[0] = 1;
    for (int i = 0; i < 4; ++i)
    for (int j = c[i]; j <= 100000; ++j)
    f[j] += f[j-c[i]];//递推求无限制时的方案数
}
void init()
{
    for (int i = 0; i < 4; ++i)
    scanf("%d", &lim[i]);
    scanf("%d", &m);
}
void dfs(int x, int sum, int st)
{
    if (sum < 0) return;//不存在的情况
    if (x == 4)
    {
        if (st & 1) ans -= f[sum];
        else ans += f[sum];//st代表是奇数过量还是偶数过量
        return;
    }
    dfs(x+1, sum, st);
    dfs(x+1, sum-(c[x]*(lim[x]+1)), !st);//进行当前硬币过量的决策
}
void deal()
{
    ans = 0;
    dfs(0, m, 0);//初始状态
    printf("%lld\n", ans);
}
int main()
{
    for (int i = 0; i < 4; ++i)
    scanf("%d", &c[i]);
    scanf("%d", &t);
    first();
    while(t--)
    {
        init();
        deal();
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值