數論----高效又實用的解題方法----洗牌機
一、數論的相關知識
1、整除
(1)、概念:設a是非零整數,b是整數。
如果有一個整數q,可以讓b=a*q,則a|b,也就是b是a的倍數,a是b的因數。
比如說:4|8,7|21 ........
(2)、性質:
1、若a|b,b|c,則a|c
2、若a|b,a|c,則a|b±c
3、若a|b,a|c,則a|(b*x+c*y)(x、y為整數)
4、若a|b,m≠0,則(m*a)|(m*b)
5、若a|n,b|n,且存在整數x,y使a*x+b*y=1,則(a*b)|n
6、若b=q*d+c,d|c,則d|b。(若b=q*d+c,d|b,則d|c)
這些性質看似用不到程式設計上,但是在一些題目中使用會大大降低程式設計的難度以及時間複雜度。
2、同餘
(1)、概念:設a,b均為整數,且a-b能被某個自然數m整除,則a≡b(mod m),也就是存在一個整數k使:a-b=m*k
(2)、性質:
1、a≡a (mod m)
2、若a≡b (mod m),則b≡a (mod m)
3、若a≡b (mod m),b≡c (mod m),則a≡c (mod m)
4、若a≡b (mod m),則a+c≡b+c (mod m)
5、若a≡b (mod m),則a*c≡b*c (mod m)
6、若a≡b (mod m),則a^c≡b^c (mod m)(a^b表示a的b次方)
7、分配律
(a±b)%m=(a%m±b%m)%m
(a*b)%m=(a%m*b%m)%m
除法不滿足分配律,但是滿足(a/b)%m=a%(b*n)/b
3、最大公因數與最小公倍數
(1)、概念:不用說了吧...........,a,b的最大公因數記為:GCD(a,b),a,b的最小公倍數記為:LCM(a,b)
當GCD(a,b)=1時,則a,b互質。
(2)、性質:
1、GCD(x,y)=GCD(x,y-x)=GCD(y,x%y)(輾轉相除法原理)
2、x*y=GCD(x,y)*LCM(x,y) >>>>> LCM(x,y)=x*y/GCD(x,y)
4、素數
(1)、概念:一個除1和它本身以外,沒有其他因數的數,1不是素數。
(2)、性質:
1、唯一分解定理
2、威爾遜定理:若p是素數,則(p-1)!≡-1(mod p) (其中!表示階乘)(其逆定理也成立)
3、費馬定理:若p是素數,a是正整數,且a、p互質,則a^(p-1)≡1(mod p) (其逆定理不成立)
(3)、尤拉函式:φ(n)表示1~n之間與n互質的數。
(4)、尤拉定理:
1、若p為素數,則φ(p)=p-1
2、若p為素數,則φ(p^a)=(p-1)*p^(a-1)
3、若a,b互質,則φ(a*b)=φ(a)*φ(b)
4、若a,m互質,則a^φ(m)≡1 (mod m)
講了這麼多,來看一道例題吧
二、例題
洗牌機
題目描述
有2n張牌,放在2n個從1到2n的有序位置上。洗牌機每次可以把第i張牌洗到p(i)的位置上。P(i)的定義如下:
問經過最少多少輪洗牌,才會使所有牌回到原來的位置。
輸入
輸入格式:
有多組資料,每組資料一個整數n(n<=109)
輸出
輸出格式:
對於每組資料,輸出一個整數,表示答案。
樣例輸入
1
樣例輸出
2
三、解題方法
1、我們先把p(i)的公式化成:p(i)=2*i%(2*n+1)
2、如果進行k次洗牌,則i的位置在2^k*i%(2*n+1)
3、為了運算簡便,我們把i取1,原式就轉化為:2^k%(2*n+1)
4、當p(i)再次等於1時,k就是答案,原問題就轉化為:2^k≡1 (mod 2*n+1),已知n,求最小的k。
5、因為2與2*n+1互質,由尤拉定理可知:2^φ(2n+1)≡1 (mod 2n+1),所以k=φ(2n+1)。
6、當n=3時,k=φ(2n+1)=6,但是k的最小值為3,洗牌過程如下。
1 2 3 4 5 6
4 1 5 2 6 3 (1)
2 4 6 1 3 5 (2)
1 2 3 4 5 6 (3)
7、我們可以找φ(2n+1)的因子q,如果q滿足2^q≡1 (mod 2n+1)時(通過快速冪演算法來計算),q就是k的最小值。
程式碼:
#include<cstdio>
#include<cmath>
long long m,n,x,ans;
long long P(long long x,long long y,int mod)
{
long long ans=1;
while(y){
if(y&1) ans=(ans*x)%mod;
y>>=1;x=x*x%mod;
}
return ans;
}
long long phi(long long n)
{
long long m=(long long)sqrt(n+0.5),ans=n;
for(int i=2;i<=m;i++)
if(n%i==0){
ans=ans/i*(i-1);
while(n%i==0)n/=i;
}
if(n>1)
ans=ans/n*(n-1);
return ans;
}
int main()
{
int i;
while(~scanf("%lld",&n)){
m=2*n+1;
x=phi(m);
ans=x;
for(i=2;i*i<=x;i++){
if(x%i==0){
if(P(2,i,m)==1)
if(i<=ans){ans=i;break;}
if(P(2,x/i,m)==1)
if(x/i<=ans){ans=x/i;}
}
}
printf("%lld\n",ans);
}
}