bzoj1042_递推加容斥原理

来源:互联网 发布:ubuntu如何安装eclipse 编辑:程序博客网 时间:2024/06/07 20:52

又一道容斥原理的题目。

最开始看的时候只会打暴力, 想到了正常的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 + 10using 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;}


0 0