1. 程式人生 > 實用技巧 >【題解】[SDOI2010]古代豬文

【題解】[SDOI2010]古代豬文

【題目描述】

豬王國的文明源遠流長,博大精深。

\(iPig\) 在大肥豬學校圖書館中查閱資料,得知遠古時期豬文文字總個數為 \(n\)。當然,一種語言如果字數很多,字典也相應會很大。當時的豬王國國王考慮到如果修一本字典,規模有可能遠遠超過康熙字典,花費的豬力、物力將難以估量。故考慮再三沒有進行這一項勞豬傷財之舉。當然,豬王國的文字後來隨著歷史變遷逐漸進行了簡化,去掉了一些不常用的字。

\(iPig\) 打算研究古時某個朝代的豬文文字。根據相關文獻記載,那個朝代流傳的豬文文字恰好為遠古時期的 \(1/k\),其中 \(k\)\(n\) 的一個正約數(可以是 \(1\)\(n\))。不過具體是哪 \(1/k\)

,以及 \(k\) 是多少,由於歷史過於久遠,已經無從考證了。

\(iPig\) 覺得只要符合文獻,每一種 \(k|n\) 都是有可能的。他打算考慮到所有可能的 \(k\)。顯然當 \(k\) 等於某個定值時,該朝的豬文文字個數為 \(n/k\)。然而從 \(n\) 個文字中保留下 \(n/k\) 個的情況也是相當多的。\(iPig\) 預計,如果所有可能的 \(k\) 的所有情況數加起來為 \(p\) 的話,那麼他研究古代文字的代價將會是 \(g^p\)

現在他想知道豬王國研究古代文字的代價是多少。由於 \(iPig\) 覺得這個數字可能是天文數字,所以你只需要告訴他答案除以 \(999911659\)

的餘數就可以了。

【輸入格式】

一行兩個正整數 \(n,g\)

【輸出格式】

輸出一行一個整數表示答案。

【輸入輸出樣例】

輸入

4 2

輸出

2048

【資料規模與約定】

  • 對於 \(10\%\) 的資料,\(1\le n \le 50\)
  • 對於 \(20\%\) 的資料,\(1\le n \le 1000\)
  • 對於 \(40\%\) 的資料,\(1\le n \le 10^5\)
  • 對於 \(100\%\) 的資料,\(1\le n,g \le 10^9\)

【演算法分析】

本題實際上就是求 \(q^{\sum_{d|n}C_n^d}mod\ 999911659\)\(d\)\(n\)

的約數。

根據尤拉定理:若 \(a,n\) 互質,對於任意的整數 \(b\) ,有 \(a^b\equiv a^{b\ mod\ \varphi (n)}(mode\ n)\)\(\varphi (n)\) 是尤拉函式。

\(q=999911659\),則上式結果為 \(0\) ,否則,因為 \(999911659\) 是質數,與任何數互質,因此:

\(q^{\sum_{d|n}C_n^d}\equiv q^{\sum_{d|n}C_n^d\ mod \ 999911658}(mod\ 999911659)\)

現在就轉換為求解 \(q\) 的次方:\(\sum_{d|n}C_n^d\ mod \ 999911658\)

因為模數較大,若直接使用 \(Lucas\) 定理會超時。嘗試將模數縮小。

\(9999116589\) 質因數分解,可得 \(999911658=2\times 3\times 4679\times 35617\),可以使用 \(Lucas\) 定理計算組合數 \(\sum_{d|n}C_n^d\)\(2,3,4679,35617\) 分別為 \(a_1,a_2,a_3,a_4\),在計算過程中,對於質數 \(p\) ,可以預處理出\(1!\sim p!\)\(p\) ,以及階乘模 \(p\) 的逆元,可以快速求解出組合數模 \(p\) 的值。

那麼 \(\sum_{d|n}C_n^d\ mode \ 999911658\) 的解 \(x\) 滿足:

\(\left\{\begin{matrix} x\equiv a_1(mod\ 2) \\ x\equiv a_2(mod\ 3) \\ x\equiv a_2(mod\ 4679)\\ x\equiv a_n(mod\ 35617) \\ \end{matrix}\right.\)

得到 \(x\) 的最小正整數解,最後的答案就是 \(q^x\) ,可以使用快速冪求解。

本題是數論的綜合應用,尤拉定理+\(Lucas\) 定理+乘法逆元+中國剩餘定理+快速冪 。

【參考程式】

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=100010;
ll divv[N],cnt;
ll KSM(ll x,ll k,ll P)			//快速冪 
{
	ll ans=1;
	while(k)
	{
		if(k&1)	ans=(ans*x)%P;
		k>>=1;
		x=(x*x)%P;
	}
	return ans%P;
}
//組合數C(n,m)=n!/(m!*(n-m)!)=n!/(n-m)!取模乘以m!模P的逆元
//P是質數,可以使用費馬小定理 
ll C(ll n,ll m,ll P)	 
{	
	ll a=1,b=1;
	for(int i=n-m+1;i<=n;i++)	a=(a*i)%P;
	for(int i=2;i<=m;i++)	b=(b*i)%P;
	return (a*KSM(b,P-2,P))%P;	//費馬小定理求逆元 
} 
ll Lucas(ll x,ll y,ll P)
{
	if(!y) return 1; 
	if(x<y) return 0;
	if(x<P&&y<P)	return C(x,y,P);
	return (Lucas(x%P,y%P,P)*Lucas(x/P,y/P,P))%P;
}
int main()  
{  
	ll n,g;
	scanf("%lld%lld",&n,&g);
	if(g%999911659==0) {cout<<0;return 0;} 
	for(int i=1;i*i<=n;i++)			//找約數 
	{
		if(i*i==n) divv[++cnt]=n/i;
		else if(n%i==0)	divv[++cnt]=i,divv[++cnt]=n/i;
	}
	ll a1=0; 
	for(int i=1;i<=cnt;i++)
		a1=(a1+Lucas(n,divv[i],2))%2;
	ll a2=0; 
	for(int i=1;i<=cnt;i++)
		a2=(a2+Lucas(n,divv[i],3))%3;
	ll a3=0; 
	for(int i=1;i<=cnt;i++)
		a3=(a3+Lucas(n,divv[i],4679))%4679;	
	ll a4=0; 
	for(int i=1;i<=cnt;i++)
		a4=(a4+Lucas(n,divv[i],35617))%35617;
	ll P=999911658;
	//中國剩餘定理,由於2,3,679,35617是質數,可以直接使用費馬小定理求解 
	ll ans=(a1*P/2*KSM(P/2,2-2,2))%P+(a2*P/3*KSM(P/3,3-2,3))%P+(a3*P/4679*KSM(P/4679,4679-2,4679))%P+(a4*P/35617*KSM(P/35617,35617-2,35617))%P;
	ans=((ans%P)+P)%P;		//取最小正整數 
	cout<<KSM(g,ans,P+1)<<endl;
	return 0;  
}