[DP?]素数筛+Lucas定理+费马小定理
Description
在杨辉三角中,从第一行第一列\((0,0)\)开始,每次可选择向正下方走或向右下方走,走到第\(n\)行时不能超过第\(n\)行第\(k\)个元素,询问所经过路径的值的和的最小值\(mod(p)\),一共有T
组询问,\(T \leq 100000\), \(0 \leq k \leq n \leq 1e9\), 保证\(p\)是质数, 其中\(p < 1e4\)
Solution
名字虽然是DP,但是可以找出最优方案。
优先考虑\(k \leq \frac{n}{2}\)的情况,先向下走\(n-k\)步到达\((n-k-1,0)\)
再一路沿着右下方走,直到到达底部,即有\(C_{n-k}^{0}+C_{n-k+1}^{1}+C_{n-k+2}^{2}+...+C_{n}^{k}=C_{n+1}^{k}\)
通过变换\(C_{n-k}^{0} = C_{n-k+1}{0}\)
再通过公式\(C_{n}^{m}=C_{n-1}^{m}+C_{n-1}^{m-1}\)可将上述公式合并
得到\(C_{n+1}^{k}\)
所以答案为\(C_{n+1}^{k}+n-k\)
当\(k > \frac{n}{2}\)时,根据对称性,令\(k=n-k\)即可转化成上一种情况
又发现题目涉及组合数取模,所以要用到费马小定理
费马小定理:
假如\(a\)是一个整数,\(p\)是一个质数,且\(gcd(a,p)=1\),即\(a,p\)互质,那么有\(a^{p−1}≡1(modp)\)
已知\(a^{p-1}≡1\),可以得到\(a \cdot a^{p-2}≡1\),我们称\(a\)和\(a^{p-2}\)为在\(mod(p)\)意义下的乘法逆元
然而这只解决了除法取模的问题,注意到\(n\)的范围在\(1e9\)直接计算组合数又是铁套老鹅(TLE),于是借助Lucas定理
Lucas定理
对于质数\(p\),有\(C_n^m\ mod \ p = C_{\lfloor \frac{n}{p} \rfloor}^{\lfloor \frac{m}{p} \rfloor} \cdot C_{n\ mod\ p}^{m\ mod\ p} \ mod \ p\)
可知\(n\ mod\ p\)和\(m\ mod\ p\)一定是小于\(p\)的数,可直接求解,其余部分继续用Lucas定理求解,当\(m=0\)的时候返回\(1\)
long long Lucas(long long n, long long m, long long p) {
if (m == 0) return 1;
return (C(n % p, m % p, p) * Lucas(n / p, m / p, p)) % p;
}
有了这些工具之后就可以预处理阶乘和逆元了
Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
#include <cstring>
#include <queue>
typedef long long LL;
const int MAXN = 10000;
using namespace std;
LL N,K,MOD;
bool isnotp[MAXN + 10];
LL pri[1500], num = 0;//素数表
int f[1300][MAXN];//阶乘
int inv[1300][MAXN];//逆元
inline void Euler() {
isnotp[1] = true;
memset(isnotp, false, sizeof(isnotp));
for (int i = 2; i <= MAXN; i++) {
if (!isnotp[i]) pri[++num] = i;
for (int j = 1; j <= num && i*pri[j] <= MAXN; j++) {
isnotp[i*pri[j]] = true;
if (i%pri[j]==0) break;
}
}
}
LL fffpow(LL x, LL y, LL pp) {
LL sum = 1;
LL a = x;
while (y) {
if (y&1) {
sum = (sum*a) % pp;
}
a = (a*a)%pp;
y>>=1;
}
return (sum)%pp;
}
int cnt = 0;
inline LL Lucas(int N,int M,int o)
{
LL a,b,ans=1;
while(N && M) {
a = N%pri[o];
b = M%pri[o];
if(a < b)return 0;
ans = ans*f[o][a]%pri[o]*inv[o][b]%pri[o]*inv[o][a-b]%pri[o];
N /= pri[o];
M /= pri[o];
}
return ans;
}
int main() {
Euler();
for(int i = 1; i <= num; i++) {
f[i][0] = f[i][1] = 1;
inv[i][0] = inv[i][1] = 1;
for(int j = 2;j < pri[i]; j++) {
f[i][j] = f[i][j-1]*j % pri[i];
inv[i][j] = fffpow(f[i][j], pri[i]-2, pri[i]);
}
}
while (cin >> N >> K >> MOD) {
LL ans;
if(K > N/2) K = N - K;
int l = 1, r = 1229;
while(l <= r) {
int mid = (l + r) >> 1;
if(MOD < pri[mid]) r = mid - 1;
else l = mid + 1;
}
ans = (Lucas(N + 1, K, l - 1) + N - K)%MOD;
printf("Case #%d: %lld\n", ++cnt, ans);
}
return 0;
}