CF896D Nephren Runs a Cinema(组合计数+数论)

链接CF896D Nephren Runs a Cinema

大爱奈芙莲。。。。

个人感觉这题和 P3266 [JLOI2015]骗我呢有点像。
首先可以考虑dp,\(f[i][j]\)为排到第 i 个人,当前剩余 j 张50元钱的方案数,转移方程显然,超时也显然。

受P3266的启发,考虑放到二维坐标系中,x坐标表示排到了哪一位,y坐标表示剩余50元钱的数量,先不考虑vip用户,假设最后剩余 p 张50元钱,那么方案数就是从\((0, 0)\)向右上或向右下走到\((n, p)\) 的方案数,在 n 步中,向上的步数 - 向下的步数 = p,向上的步数 + 向下的步数 = n,可解得方案数为\(C_{n}^{(n - p) / 2}\), 其中有不合法的方案数,即触碰了直线\(y = -1\)的路径,我们需要减去从\((0, -2)\)\((n, p)\) 的方案数,即\(C_{n}^{(n - p) / 2 - 1}\)。最后因为从 l 到 r 是连续的,所以我们将所有式子算出来相加可以消掉中间项,最后只剩\(C_{n}^{(n - l) / 2} - C_{n} ^ {(n - r - 1) / 2}\),我们需要枚举vip用户数量,最算的时候把总数减去vip数量,求出的结果乘上\(C_{n}^{i}\) (i为vip数量)。

因为模数不是质数,无法用肥妈小定理,用exgcd也有可能找不到逆元,所以我们将模数 p 质因数分解,在预处理阶乘的时候把包含的 p 的质因子取出来,记下指数,然后再乘回来。算逆元直接用欧拉定理。
代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long lld;
const int N = 100005;
lld p, n, l, r;
lld pr[N], inv[N], f[N], c[N][40], phi;
//pr[]为p的质因子,c[i][j]为i的阶乘中p的第j个质因子的指数
int cnt = 0;
lld powe(lld a, lld b) {
    lld base = 1;
    while(b) {
        if(b & 1) base = (base * a) % p;
        a = (a * a) % p;
        b >>= 1;
    }
    return base;
}
void calc() {
    f[0] = inv[0] = 1;
    f[1] = inv[1] = 1;
    lld x = p;
    for(lld i = 2; i * i <= x; i++) {//分解p
        if(x % i) continue;
        pr[++cnt] = i;
        while(!(x % i)) x /= i;
    }
    if(x > 1) pr[++cnt] = x;
    for(int i = 2; i <= n; i++) {
        lld x = i;
        for(int j = 1; j <= cnt; j++) {
            c[i][j] = c[i - 1][j];//指数加上之前的
            while(!(x % pr[j])) x /= pr[j], c[i][j]++;
        }
        f[i] = f[i - 1] * x % p;//阶乘
        inv[i] = powe(f[i], phi - 1);//逆元
    }
}
lld comb(lld _n, lld m) {
    if(m < 0 || _n < m || n <= 0) return 0;
    if(m == 0) return 1;
    lld tmp = f[_n] * inv[m] % p * inv[_n - m] % p;
    for(int i = 1; i <= cnt; i++) {
        tmp = (tmp * powe(pr[i], c[_n][i] - c[m][i] - c[_n - m][i])) % p;//乘回来
    }
    return tmp;
}
int main() {
//  freopen("data.in", "r", stdin);
    scanf("%lld%lld%lld%lld", &n, &p, &l, &r);
    phi = p; lld d = p;//算phi函数
    for(lld i = 2; i * i <= d; i++) {
        if(d % i) continue;
        phi = phi / i * (i - 1);
        while(!(d % i)) d /= i;
    }
    if(d > 1) phi = phi / d * (d - 1);
    calc(); lld ans = 0; 
    r = min(n, r);
    for(lld i = 0; i <= n - l; i++) {
        lld tmp = (comb(n - i, (n - i - l) >> 1) - comb(n - i, (n - i - r - 1) >> 1) + p) % p;
        ans += (tmp * comb(n, i)) % p; 
        ans = ans % p;
    }
    printf("%lld", ans);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/mcggvc/p/12466581.html