【bzoj1951】[Sdoi2010]古代猪文 费马小定理+Lucas定理+中国剩余定理
题目描述
求 $g^{\sum\limits_{k|n}C_{n}^{\frac nk}}\mod 999911659$
输入
有且仅有一行:两个数N、G,用一个空格分开。
输出
有且仅有一行:一个数,表示答案除以999911659的余数。
样例输入
4 2
样例输出
2048
题解
费马小定理+Lucas定理+中国剩余定理
首先由费马小定理$a^{p-1}\equiv 1\ \ (mod\ p)$,可以将模数转化到答案的指数上,即求$\sum\limits_{k|n}C_{n}^{\frac nk}\ mod\ 999911658\ =\ \sum\limits_{k|n}C_{n}^{k}\ mod\ 999911658$。
然后直接$O(\sqrt n)$枚举一对约数中较小的那个,直接计算即可。
但这里有一个问题:减1以后模数不是质数。
于是我们需要将其进行质因子分解:$999911658=2*3*4679*35617$,然后先使用Lucas定理算出$C_n^k$在模其质因子意义下的结果,然后再使用中国剩余定理合并模线性同余方程组即可。在求解时,由于一定有解,因此不需要判定解得合法性,直接求逆元除过去即可。
时间复杂度$O(\sqrt{n}\log^2n)$
注意本题的一个巨大坑点:当$a\ mod\ p=0$时费马小定理不成立(底数模意义下为0,指数不为0但模意义下为0时会计算为1,而实际上0的正整数次幂应该为0),因此需要特判这种情况。
#include <cstdio> #define MOD 999911658 typedef long long ll; const int mod[] = {2 , 3 , 4679 , 35617}; ll fac[4][36000]; inline ll pow(ll x , ll y , int mod) { ll ans = 1; while(y) { if(y & 1) ans = ans * x % mod; x = x * x % mod , y >>= 1; } return ans; } ll calc(int n , int m , int p) { if(n < m) return 0; if(n < mod[p]) return fac[p][n] * pow(fac[p][m] , mod[p] - 2 , mod[p]) * pow(fac[p][n - m] , mod[p] - 2 , mod[p]) % mod[p]; return calc(n / mod[p] , m / mod[p] , p) * calc(n % mod[p] , m % mod[p] , p) % mod[p]; } ll C(int n , int m) { int i; ll ans = 0; for(i = 0 ; i < 4 ; i ++ ) ans = (ans + MOD / mod[i] * pow(MOD / mod[i] , mod[i] - 2 , mod[i]) % MOD * calc(n , m , i)) % MOD; return ans; } int main() { int n , g , i , j; ll sum = 0; scanf("%d%d" , &n , &g) , g %= MOD + 1; if(!g) { puts("0"); return 0; } for(i = 0 ; i < 4 ; i ++ ) { fac[i][0] = 1; for(j = 1 ; j < mod[i] ; j ++ ) fac[i][j] = fac[i][j - 1] * j % mod[i]; } for(i = 1 ; i * i <= n ; i ++ ) { if(n % i == 0) { sum = (sum + C(n , i)) % MOD; if(i * i != n) sum = (sum + C(n , n / i)) % MOD; } } printf("%lld\n" , pow(g , sum , MOD + 1)); return 0; }