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

 

 

posted @ 2017-10-13 14:48  GXZlegend  阅读(329)  评论(0编辑  收藏  举报