JZOJ3704 自古枪兵幸运E 题解

题面

Description
俗话说,自古枪兵幸运E。而身为幸运E 的L 叔最想要的,就是C 妈的金羊毛了。然而这显然不是那么容易达成的。为了防止入侵者,C 妈花费了k 单位魔力,建造了包含了若干个消耗一单位魔力的小型防御设施和消耗两单位魔力的
大型防御设施。
经过很长时间的调查,L 叔终于了解到了C 妈可能采用的小型防御设施有n种,大型防御设施有m 种。每一种都可以建造任意个数个。由于L 叔拥有Rune文字的加持和B 级别的对魔力,只要了解每一种防御设施的数量,就可以破解
这样的防御。
然而,因为L 叔的幸运是E,所以他只有在尝试每一种方案之后才能找到破解的方法。他找到你,想让你判断一下他要尝试多少次才找到破解方法。由于凯尔特神话中对数字的迷信,你只要输出答案模p 的值就可以了。

Input
第一行一个正整数t,表示数据组数。
接下来t 行每行四个非负整数n, m, k, p。

Output
t 行每行一个整数,表示答案模p 的值。

Sample Input
3
0 10 2 47
2 2 4 47
5 5 10 47

Sample Output
10
14
6

Data Constraint
对于30%的数据,k ≤ 10^5。
对于60%的数据,n, m ≤ 10^3,p ≤ 2 × 10^4。
对于100%的数据,t ≤ 20,n, m ≤ 10^5,k ≤ 10^12,p ≤ 10^6,p 为质数。


题目大意

n种大小为1的物品,每种有无限个.
m种大小为2的物品,每种有无限个.
求填满大小为k的背包的方案数mod\ P.

思路

对于30\%的数据,直接做背包。60\%的数据也差不多。
对于100\%的数据,k的大小显然是不能背包了,考虑构造生成函数来表示n种和m种物品。
先考虑大小为1的物品,其有无限个,因此这类物品的生成函数就是:
(1+x+x^2+x^3+...)=\frac{1}{1-x}
这类物品有n种,因此这n种的组合就是:
(\frac{1}{1-x})^n
同样的,第二类物品的生成函数为:
(1+x^2+x^4+x^6+...)=\frac{1}{1-x^2}
m种组合起来就是:
(\frac{1}{1-x^2})^m
将两类物品组合就是:
(\frac{1}{1-x})^n(\frac{1}{1-x^2})^m
我们现在就是要求这个式子展开后x^k项的系数。但是两个式子展开后都是无限项的多项式,枚举复杂度是O(k)的。我们考虑让这个式子变形一下,令其出现一个有限项的多项式:
(\frac{1}{1-x})^n(\frac{1}{1-x^2})^m =(1-x)^{-n}(1-x^2)^{-m} =(1-x)^{-n}(1+x)^{-n}(1-x^2)^{-m}(1+x)^n =(1-x^2)^{-n-m}(1+x)^n
后面一项其实就是二项式的n次方,可以展开成n+1项,于是我们就可以枚举后面一项展开后的每一项x^i,找到前面一项里x^{k-i}的系数,将他们的系数相乘就是答案,计算组合数要用到Lucas定理(k很大)。
前面一项的展开需要使用广义二项式定理:
(1-x)^{-n}=\sum_{i=0}^{\infty}C_{n+i-1}^{n-1}x^i
于是:
(1-x^2)^{-n-m}=(1-x^2)^{-(n+m)}=\sum_{i=0}^{\infty}C_{n+m+i-1}^{n+m-1}x^{2i}
也就是这个多项式中x的指数只可能是偶数,我们就要在枚举里跳过掉k-i不是偶数的情况。
代码:

#include 
#include 
#include 

typedef long long ll;
const ll P = 1e6 + 7;
int T;
ll n, m, k, p, ans, fac[P], inv[P];

ll C(ll n, ll m)
{
    if (n < m) return 0;
    return fac[n] * inv[m] % p * inv[n - m] % p;
}
ll Lucas(ll n, ll m)
{
    if (n < p && m < p) return C(n, m);
    return Lucas(n % p, m % p) * Lucas(n / p, m / p);
}

int main()
{
    freopen("luckye.in", "r", stdin);
    freopen("luckye.out", "w", stdout);

    scanf("%d", &T);
    fac[0] = inv[0] = inv[1] = 1;
    while (T--)
    {
        scanf("%lld%lld%lld%lld", &n, &m, &k, &p);
        for (int i = 2; i <= p; i++) inv[i] = (p - p / i) * inv[p % i] % p;
        for (int i = 2; i <= p; i++) inv[i] = inv[i] * inv[i - 1] % p; //阶乘的逆元
        for (int i = 1; i <= p; i++) fac[i] = fac[i - 1] * i % p;
        ans = 0;
        for (int i = 0, x; i <= n; i++)
        {
            if ((k - i) & 1) continue;
            ans = (ans + Lucas(n, i) * Lucas(n + m + ((k - i) / 2) - 1, n + m - 1) % p) % p;
        }
        printf("%lld\n", ans);
    }

    fclose(stdin);
    fclose(stdout);
    return 0;
}

你可能感兴趣的:(JZOJ3704 自古枪兵幸运E 题解)