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;
}