洛谷·[SDOI2010]古代猪文

初见安~这里是传送门:洛谷P2480 古代猪文其实这个题咕了好久了才写……

题解

【本题有种数论知识点大杂烩的感觉,若有知识点残缺欢迎来走走:定理传送门Lucas传送门

数论题啊……一眼下来连题都读不懂呢。样例知道怎么来的了就很好整了。

说白了题意就是求G^{\sum_{d|n}C_n^d} mod 999911659

n的范围是1e9,所以对于因数d的枚举不会超过\sqrt{n},可以暴力找;但是求组合数就爆掉了。因为模数很大,n也很大,你预处理fac会爆空间,直接Lucas作用又不大……所以我们就要把模数降下来

可以想到欧拉定理——a^b \equiv a^{b mod \phi(n)}(modn),且a和n互质。//这里搁浅一下,文末继续

因为999911659是质数,所以\phi(999911659) = 999911658 = 2*3*4679*35617

所以问题就可以转化为求:

\sum_{d|n}C_n^d mod 999911658

对于把模数降下来,我们引入一个定理::

对于两个整数x,y,如果同余一个数p,那么同样同余这个数的所有因数

例如x \equiv y(mod p),p=a*b,则一定有x \equiv y(mod a),x \equiv y(mod b)

证明显然【严谨公式证明有点儿困难,就口胡了……】:

  • a = t+k_1p, b=t+k_2p,则有a\equiv b \equiv t(mod p)
  • 设x是p的某个因数,则必有k_1p \equiv k_2p \equiv 0(modx)
  • 所以a和b都只剩下t,同一个数取余同一个数等于同一个数;
  • 证毕。

所以我们设ans = \sum_{d|n}C_n^d mod 999911658,则ans与这一坨sigma同余。

所以可以转化到:

\left\{\begin{matrix} ans\equiv a_1(mod 2)\\ ans \equiv a_2(mod 3)\\ ans \equiv a_3(mod 4679)\\ ans \equiv a_4(mod 35617) \end{matrix}\right.

也就是说我们最后要求的ans就是上面这个方程式的最小正整数解了。对于a_1,a_2,a_3,a_4我们可以暴力求组合数,因为模数已经很小了所以方法很多。再然后用中国剩余定理(CRT)求解即可。

到这里其实这个题就差不多了。但是最后的最后你可能只有95分,WA 第13个点。为什么??!

我们回到前面搁浅的那个问题上来吧。因为欧拉定理的互质条件,所以我们可以特判一下:如果不互质,直接输出0,或者写一个扩展欧拉定理——求a^b (modn)当a和n不互质并且b>=\phi(n)的时候,有a^b \equiv a^{b mod \phi(n)+\phi(n)}(modn)。这里就顺便解释一下代码中特别注释的做法:【直接选择特判的可以跳过】

因为在欧拉定理下,有三种情况:

  • a、n互质,a^b \equiv a^{b mod\phi(n)}(modn)
  • a、n不互质,并且b>=\phi(n)a^b \equiv a^{b mod \phi(n)+\phi(n)}(modn)
  • a、n不互质,并且b<\phi(n),直接暴力求a^b即可。

也就是说,除了第三种情况,我们都可以直接用a^b \equiv a^{b mod \phi(n)+\phi(n)}(modn)【因为a^{\phi(n)} \equiv 1(mod n)(gcd(a,n) = 1)】。而很巧的是,对于这个题,因为999911659是个质数,所以最后的指数在999911659以内时两者互质,都是第一种情况,可以直接套用;而与它不互质的时候,指数早就大于1e9了,一定会被mod下来,所以只存在第一种和第二种情况,我们输出答案的时候直接套这个公式是可行的呢!!!【当然一般情况下最好不要这么冒险】

最后的最后【其实您已经可以不用看了】,本狸因为tcl所以在求组合数这个问题上纠结了很久……因为我Lucas的那篇文章的求法并不适用了,但是用预处理fac和fac的inv的方法也不行,inv从后往前推过来全是0【QuQ我也不知道为什么】,所以只能用费马小,mod-2次方来求逆元……

说了这么多,也可以上代码了——

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#define maxn 40005
using namespace std;
typedef long long ll;
ll read() {
	ll x = 0, f = 1, ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x * f;
}
ll mod[5] = {999911659, 2, 3, 4679, 35617}, a[5];
ll fac[maxn];
ll pw(ll a, ll b, ll p) {ll res = 1; a %= p; while(b) {if(b & 1) res = res * a % p; a = a * a % p, b >>= 1;} return res;}

ll C(ll n, ll m, ll p) {
	if(n < m) return 0; if(!m) return 1;
	return fac[n] * pw(fac[m], p - 2, p) % p * pw(fac[n - m], p - 2, p) % p;
}

ll Lucas(ll n, ll m, ll p) {if(n < m) return 0; return m? C(n % p, m % p, p) * Lucas(n / p, m / p, p) % p : 1;}
ll ans = 0;
void CRT() {//中国剩余定理 
	ll M = mod[0] - 1, x, y;//这里M是\phi(n_,所以要-1 
	for(int i = 1; i <= 4; i++) 
		ans = (ans + M / mod[i] * a[i] % M * pw(M / mod[i], mod[i] - 2, mod[i]) % M) % M;
}

signed main() {
	ll n, G;
	n = read(), G = read();
    if(G % mod[0] == 0) {puts("0"); return 0;}//这里是直接特判的版本
	for(int t = 1; t <= 4; t++) {
		fac[0] = fac[1] = 1;
		for(ll i = 2; i <= 40000; i++) fac[i] = fac[i - 1] * i % mod[t];//这里写的是4e4,其实卡着mod写也行	
		for(ll i = 1; i * i <= n; i++) if(n % i == 0) {
			a[t] = (a[t] + Lucas(n, i, mod[t])) % mod[t];//暴力求和 
			if(i * i != n) a[t] = (a[t] + Lucas(n, n / i, mod[t])) % mod[t];
		}
	}
	CRT();
//	printf("%lld\n", pw(G, ans + mod[0] - 1, mod[0])); 这就是直接套用欧拉公式的版本
    printf("%lld\n", pw(G, ans, mod[0]));
	return 0;
}

真的写了好久……哎。

迎评:)
——End——

发布了158 篇原创文章 · 获赞 23 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_43326267/article/details/102928567