目錄
快速冪
題目描述
【題意】
求a^b mod c,a,b,c都是整數。
【輸入格式】
一行三個整數 a、b、c。 0 ≤ a,b,c ≤ 10^9
【輸出格式】
一行,a^b mod c的值。
【樣例輸入】
2 5 7
【樣例輸出】
4
題目鏈接就不放出來了,因爲這個OJ已經很卡了,且不登錄不能看題目。
其實,我們容易知道,冪次有個性質:當時
用腦子想想就知道。
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long LL;
LL kms(LL a,LL b,LL c)//a^b%c
{
LL ans=1%c;/*防止c==1*/a%=c;
while(b)//b!=0
{
if(b&1)ans=(ans*a)%c;
a=(a*a)%c;b>>=1;//b/=2;
}
return ans;
}
int main()
{
LL a,b,c;scanf("%lld%lld%lld",&a,&b,&c);
printf("%lld\n",kms(a,b,c));
return 0;
}
擴展歐幾里得
GCD
如果我們要求兩個數字的最大公約數怎麼求?
如果兩個數字存在最大公約數,那麼把寫成,把寫成
那麼我們知道,但是又與互質,所以僅當時,爲最大公約數,此時也就是最大公約數,而且我們也知道也是與x互質的。
所以我們可以設新的與新的,,,當時,就是最大公約數。
而且一開始也不怕,做了一次交換後就換過來了()
int gcd(int x,int y)
{
if(x==0)return y;
return gcd(y%x,x);
}
擴展歐幾里得
如何求的一個整數解?
盡看擴展歐幾里得。
首先,我們設
那我們就想了,如果有個方程爲,不就很妙了嗎?
隨便等於什麼都不影響最終結果,只不過一般取0,怕爆long long。
然後我們再證一個:
當我們解出了,怎麼推到?
我們可以推導一下(爲向下取整):
而就是C++的
所以,我們發現和就是過程,所以最後也會得到和
然後再看:如果的話,設,然後把直接乘以就沒什麼毛病了。
但是如果不整除就無解,我們又可以證一證:
首先是肯定有解的,我們可以知道,而且可以類似於,我們可以根據同餘的性質得出我們可以把的同一個因數提出,得到,化成,而且a’與b’不互質,那麼我們可以得出,同餘方程,互質時有解,或者當整除時有解。(好像說偏了)
然後,如果與c不整除,我們再設
那麼我們把原式化爲:同除可得
但是所以是個分數,但是都是整數,所以無解
至此,結束了(感覺又過了一個世紀)!
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long LL;
LL exgcd(LL a,LL b,LL &x,LL &y/*引用*/)
{
if(a==0)
{
x=0;y=1;//0x+gcd(a,b)y=gcd(a,b)
return b;
}
else
{
LL tx,ty;
LL d=exgcd(b%a,a,tx,ty);
x=ty-(b/a)*tx;//公式
y=tx;//公式
return d;
}
}
int main()
{
LL a,b,c,x,y;scanf("%lld%lld%lld",&a,&b,&c);
LL d=exgcd(a,b,x,y);
if(c%d!=0)printf("no solution!\n");
else
{
c/=d;
printf("%lld %lld\n",x*c,y*c);
}
return 0;
}
其實也沒什麼難度。
同餘系列
同餘方程
題目描述
【題意】
已知a,b,m,求x的最小正整數解,使得ax≡b(mod m)
【輸入格式】
一行三個整數 a,b,m。 1 ≤ a,b,m ≤ 10^9
【輸出格式】
一行一個整數x,無解輸出"no solution!"
【樣例輸入】
2 5 7
【樣例輸出】
6
我們會發現( )其實就是也就是(的正負無多大必要,反正只是求,而且方便的地方在於爲正數)
然後我們運用擴展歐幾里德求出,但是如何求出最小的?
學過不定式的人都知道,當減時,也可以相應的加時(就是的最小公倍數),能得出另外一組整數解,也就是,又因爲,所以就是(是)。
而且是能同時被整除的最小的數字。
所以,我們可以用得出最小的正整數解
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long LL;
LL exgcd(LL a,LL b,LL &x,LL &y)
{
if(a==0)
{
x=0;y=1;
return b;
}
else
{
LL tx,ty;
LL d=exgcd(b%a,a,tx,ty);
x=ty-(b/a)*tx;
y=tx;
return d;
}
}
LL zbs(LL x){return x<0?-x:x;}
int main()
{
LL a,b,c,x,y;scanf("%lld%lld%lld",&a,&c,&b);
LL d=exgcd(a,b,x,y);//求擴展歐幾里得
if(c%d!=0)printf("no solution!\n");
else
{
c/=d;
x*=c;
LL k=b/d;
x=((x%k)+k)%k;//最小的正整數解
printf("%lld\n",x);
}
return 0;
}
同餘方程組
題目描述
【題意】
同餘方程是這樣的:已知a,b,n,求x的最小正整數解,使得ax=b(mod m)
同餘方程組是這樣:也是求x的最小正整數解,但已知b數組和m數組的情況下,
x=b[1](mod m[1]),
x=b[2](mod m[2]),
x=b[3](mod m[3]),
~~~~~~
x=b[n](mod m[n])
【輸入格式】
一行一個整數 n(1<=n<=?)
下來n行每行兩個整數b[i],m[i]。 1 ≤b[i],m[i] ≤ 10^9
【輸出格式】
一行一個整數x,無解輸出"no solution!"
【樣例輸入】
3
3 5
2 3
2 7
【樣例輸出】
23
這道題就十分的友(e)善(xin)了
現在我們可以列出
我們拿上面減下面的出:也就是的形式。
那麼我們寫成(這裏是也無所謂,但是是正數後面算最小正整數比較方便。)。
然後解出,將帶入得出了的其中一個解,而且我們又知道要變只能加上或減去,所以就只能加減,所以我們又可以列出一個新的方程:( )(其中的表示的是x的其中一組解),爲了後面的方便,我們把這個方程代替爲第二個方程,後面就是個二三做一次,三四做一次…
然後我們就可以拿這個方程與第三個方程做這種事,重複如此,最後的就是答案,但是,別忘了是求最小整數解。
求最小整數解有兩種方法:
我常用的方法:
開局直接把每個(不包括求出來的)模,並且把每次求出的都做一次最小整數解,那麼得出來的新的方程的一定小於新的,爲什麼,爲,而爲得出的。(這裏的表示,表示),我們知道,那麼,,那麼,所以小於,也就是不用模了。
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long LL;
LL exgcd(LL a,LL b,LL &x,LL &y)
{
if(a==0)
{
x=0;y=1;
return b;
}
else
{
LL tx,ty;
LL d=exgcd(b%a,a,tx,ty);
x=ty-(b/a)*tx;
y=tx;
return d;
}
}
LL a1,b1;
bool solve(LL a2,LL b2)
{
LL a=a1,b=a2,c=b2-b1,x,y;
LL d=exgcd(a,b,x,y);
if(c%d!=0)return false;
else
{
c/=d;
x*=c;
LL lca=b/d;
x=((x%lca)+lca)%lca;//最小正整數解
b1=a1*x+b1;a1=a*b/d;//不用模
return true;
}
}
int main()
{
int n;scanf("%d",&n);
scanf("%lld%lld",&b1,&a1);b1%=a1;//開局模一模
for(int i=2;i<=n;i++)
{
LL a,b;scanf("%lld%lld",&b,&a);b%=a;//開局模一模
if(!solve(a,b))
{
printf("no solution!\n");
return 0;
}
}
printf("%lld\n",b1/*不用模*/);
return 0;
}
但是後面,我又發現了一個不是很麻煩的方法,就是結尾模一下就行了。。。
//少了去正過程,中間會有許多數字變成負數
//而且更容易爆long long
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long LL;
LL exgcd(LL a,LL b,LL &x,LL &y)
{
if(a==0)
{
x=0;y=1;
return b;
}
else
{
LL tx,ty;
LL d=exgcd(b%a,a,tx,ty);
x=ty-(b/a)*tx;
y=tx;
return d;
}
}
LL zbs(LL x){return x<0?-x:x;}
LL a1,b1;
bool solve(LL a2,LL b2)
{
LL a=a1,b=a2,c=b2-b1,x,y;
LL d=exgcd(a,b,x,y);
if(c%d!=0)return false;
else
{
c/=d;
x*=c;//可能爲負數
b1=a1*x+b1;a1=a*b/d;
return true;
}
}
int main()
{
int n;scanf("%d",&n);
scanf("%lld%lld",&b1,&a1);
for(int i=2;i<=n;i++)
{
LL a,b;scanf("%lld%lld",&b,&a);
if(!solve(a,b))
{
printf("no solution!\n");
return 0;
}
}
LL zhl=a1<0?-a1:a1;//絕對值
printf("%lld\n",(b1%zhl+zhl)%zhl/*尾巴模一模*/);
return 0;
}
以上便是作者的心得。
一點想法
當然,如果前面也有係數怎麼辦?我們求出的值可能不能整除於的係數?
在我們求完時,我們得到的最後一個方程就是結果。(這裏爲方便直接把叫,叫)
( )
什麼時候是整數?
是不是當( )的時候?(爲任意整數)
那麼我們解出這個方程的的最小整數解就行了。
當然,這只是本蒟蒻的一點點假設,並沒有實踐過。
高次同餘方程
BSGS
【題意】
求解 A^x=B(mod C) (0<=x<C),求x的值。
已知:C、A、B都是正整數,2 <= C < 2^31, 2 <= A < C, 1 <= B < C。
(A與C互質)
【輸入格式】
多組測試數據。
每組數據一行三個整數:C,A,B。
【輸出格式】
每組數據一行,如果有解輸出x的最小正整數解,否則輸出"no solution!"。
【樣例輸入】
5 2 1
5 2 2
5 2 3
5 2 4
5 3 1
5 3 2
5 3 3
5 3 4
5 4 1
5 4 2
5 4 3
5 4 4
12345701 2 1111111
1111111121 65537 1111111111
【樣例輸出】
0
1
3
2
0
3
1
2
0
no solution!
no solution!
1
9584351
462803587
,又名北上廣深。
你是不是覺得這個東西十分的高大上?
其實還挺簡單的QAQ。
我們知道
我們設(向上取整,在C++的cmath庫內)
爲什麼是,學完你就知道了。
那麼可以寫成的形式
也就是
推導一波:
出現了點倪端了,不知道大家有沒有學過歐拉定理?
(當與互質的,爲歐拉函數,後面會講)
所以最大爲,爲了方便,我們不在來找,而是在裏面找,因爲是找最小的,所以在兩個範圍找都沒事,但更方便。
當然,這裏你得會Hash表(map也可以,或者像我們機房的一個大佬,不會Hash表,但會SBT)。
然後,我們枚舉從到,並把放進Hash表,然後在枚舉從到,在Hash表裏面找到最大的(爲了求最小的)滿足,那麼就可以返回答案了。
而且從開始,可以讓,不會出現負數,並且可以很好的枚舉從到的所有數字。
#include<cstdio>
#include<cstring>
#include<cmath>
#define N 2800000
using namespace std;
typedef long long LL;
class hash
{
public:
struct node
{
LL c;int x,next;
}a[N];int last[N],len,h;
hash(){h=2750159;/*用線性篩篩出的素數*/}
void ins(int x,LL c){LL y=c%h;len++;a[len].x=x;a[len].c=c;a[len].next=last[y];last[y]=len;}
int find(LL c)
{
LL x=c%h;
for(int k=last[x];k;k=a[k].next)
{
if(a[k].c==c)return a[k].x;//求最大的b
}
return -1;
}
void clear(){memset(last,0,sizeof(last));len=0;}
}zjj;
LL kms(LL a,LL b,LL c)//快速冪
{
LL ans=1%c;a%=c;
while(b)
{
if(b&1)ans=(ans*a)%c;
a=(a*a)%c;b>>=1;
}
return ans;
}
LL bsgs(LL a,LL b,LL c)
{
zjj.clear();
LL d=ceil(sqrt(c));
LL now=b;
for(int i=0;i<=d;i++)
{
zjj.ins(i,now);
now=(now*a)%c;
}//枚舉b
LL lim=now=kms(a,d,c);
for(int i=1;i<=d;i++)
{
int x;
if((x=zjj.find(now))!=-1)return i*d-x;
now=(now*lim)%c;
}//枚舉a
return -1;
}
int main()
{
LL a,b,c;
while(scanf("%lld%lld%lld",&c,&a,&b)!=EOF)
{
int ans=bsgs(a,b,c);
if(ans==-1)printf("no solution!\n");
else printf("%d\n",ans);
}
return 0;
}
exBSGS
我們會發現上面的過程有一個限制,就是必須互質,不是,這就太苛刻了吧,毒瘤出題人會有歪念頭的!
於是,我們想到了同餘的性質:,當有同一個公因子時,可以把消掉,得出,那麼,我們也可以把的最大公因數提出呀,但是也必須要整除於這個最大公因數,否則就是無解的,很容易想,我們設的最大公因數爲,然後原式就等於如果與還是不互質,我們就繼續,我們設的係數爲,那麼就在此過程中不斷增加,且減的也越來越多。
當然,此過程存在一個噁心的特判,就是當時,直接返回現在提了幾次最大公因數,也就是減了多少。
那麼,最後,我們只需要計算的時候再乘就行了,然後算出後再加上就行了。
這也是我在對的第一道黑題(雖然插頭DP也很多,但沒交)
#include<cstdio>
#include<cstring>
#include<cmath>
#define N 210000
using namespace std;
typedef long long LL;
class Hash//class多好用啊
{
public:
struct node
{
int x,next;LL c;
}a[N];int len,last[N];LL h;
Hash(){h=200043;}
void ins(int x,LL c){LL y=c%h;a[++len].x=x;a[len].c=c;a[len].next=last[y];last[y]=len;}
void clear(){len=0;memset(last,0,sizeof(last));}
int find(LL c)
{
LL x=c%h;
for(int k=last[x];k;k=a[k].next)
{
if(a[k].c==c)return a[k].x;
}
return -1;
}
}zjj;
LL ksm(LL a,LL b,LL c)
{
LL ans=1%c;a%=c;
while(b)
{
if(b&1)ans=(ans*a)%c;
a=(a*a)%c;b>>=1;
}
return ans;
}
LL gcd(LL a,LL b){return (!a?b:gcd(b%a,a));}
LL solve(LL a,LL c,LL d)
{
a%=d;c%=d;//這一步也不是很需要,但是可以更遠離爆long long
LL b=1,cnt=0,tt;
while((tt=gcd(a,d))!=1)
{
if(c==b)return cnt;//特判
if(c%tt)return -1;//無解
d/=tt;c/=tt;b=(b*a/tt)%d;a%=d;cnt++;
}
LL lim=ceil(sqrt(d*1.0)),now=c;
for(int i=0;i<=lim;i++)
{
zjj.ins(i,now);
now=(now*a)%d;
}
now=((tt=ksm(a,lim,d))*b)%d/*重複利用*/;
for(int i=1;i<=lim;i++)
{
int y=zjj.find(now);
if(y!=-1)return i*lim-y+cnt;
now=(now*tt)%d;
}
return -1;
}
int main()
{
LL a,c,d;
while(scanf("%lld%lld%lld",&a,&d,&c)!=EOF)
{
if(a==0 && d==0 && c==0)break;
zjj.clear();
LL ans=solve(a,c,d);
if(ans!=-1)printf("%lld\n",ans);
else printf("No Solution\n");
}
return 0;
}
線性篩素數
題目描述
【題意】
素數表如下:
2、3、5、7、11、13、17…………
有n次詢問,每次問第幾個素數是誰?
【輸入格式】
第一行n(1<=n<=10000)
下來n行,每行一個整數pi,表示求第pi個素數。1<=pi<=100 0000
【輸出格式】
每次詢問輸出對應的素數。
【樣例輸入】
3
1
4
7
【樣例輸出】
2
7
17
線性篩素數有兩種,埃式篩和歐拉篩。
埃式篩
我們可以知道,一個數字如果不是質數,那麼它的最小質因子一定小於等於這個數字的開平方。
很容易想到,不詳細解釋。
那麼求到的素數的話,我們可以知道,到的合數的最小質因數也是一定小於等於的,那麼我們只需要求出內的所有素數並且暴力for一遍,空間小,複雜度爲
for(int i=2;i<=sqrt(b);i++)
{
if(mp[i]==false)//爲素數
{
for(int j=i;j<=b;j+=i)mp[j]=true;
}
}
給出一種神奇卡常版:
for(register int i=3;i<=sqrt(m);i+=2)
{
if(a[i/2]==true)
{
for(register int j=i*3;j<=m;j+=i*2)a[j/2]=false;
}
}
歐拉篩
後面題目主要用歐拉篩,複雜度
這個就很神奇了,先給出代碼:
#include<cstdio>
#include<cstring>
#define N 21000000
using namespace std;
int ins[2100000];//素數表
bool mp[N];
int main()
{
for(int i=2;i<=20000000;i++)
{
if(!mp[i])ins[++ins[0]]=i;//存入素數表
for(int j=1;j<=ins[0] && ins[j]*i<=20000000;j++)
{
mp[ins[j]*i]=true;
if(i%ins[j]==0)break;
}
}
int n;scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int x;scanf("%d",&x);
printf("%d\n",ins[x]);
}
return 0;
}
許多人疑問這一句
if(i%ins[j]==0)break;
如果,那麼就可以break了,Why?那麼我們設爲,設,,我們可以知道,那麼在以後的循環,會被重複標記,那麼此時就可以break了,一句話就這麼精妙。
超級卡常版:
for(register int i=3;i<=m;i+=2)
{
if(a[i/2]==true)b[++k]=i;
for(register int j=1;j<=k && b[j]*i<=m;++j)
{
a[b[j]*i/2]=false;
if(i%b[j]==0)break;
}
}
當然,慎用卡常版。
歐拉函數
講解
就是表示在~與互質的數量,叫歐拉函數。
又記作(貌似在前面看到過呢!)
那麼我們馬上知道(當爲質數時)
但是我們又知道幾個公式去求呢?
首先,我們做一道題,設,那麼在到,與互質的數字有多少?(爲質數,且)
注意:因爲爲質數,且,那麼
那麼我們知道中整除於的數量爲,整除於的數量爲,整除於的數量爲,那麼中整除於或者整除於的數量爲
那麼與互質就是,那麼如果是求呢?
我們會發現,我們求出了以內與互質的數爲,而也就是是整除於或的,那麼,也就是說就是個循環的,也就是說。(注意:如果是的話仍然按第一個問題來看,第二個問題是針對素數有次方的情況)
那麼我們就得出了(其中的表示可分解成幾個不同的質因數,就是分解出來的不同的質因數,表示的是的次方)
那麼我們又可以得出一個公式(互質),其實這關額證明也很簡單,因爲他們互質,所以他們所含的質因子不同,那麼不會對對方產生任何影響。
當然,不僅僅止步於此,我們設一個質數,如何求?
當時,互質,所以,當是,是的其中一個質因數,根據上面可得可以合併到去得到
然後我們發現此過程可以套到歐拉篩裏面。
#include<cstdio>
#include<cstring>
#define N 21000000
using namespace std;
int ins[2100000]/*素數表*/,phi[N]/*歐拉函數*/;
int main()
{
for(int i=2;i<=20000000;i++)
{
if(!phi[i])ins[++ins[0]]=i,phi[i]=i-1;//不用mp,直接用歐拉函數的數組判斷
for(int j=1;j<=ins[0] && ins[j]*i<=20000000;j++)
{
if(i%ins[j]==0)//phi(ax)=phi(a)x
{
phi[ins[j]*i]=ins[j]*phi[i];
break;
}
else phi[ins[j]*i]=phi[ins[j]]*phi[i];//phi(ax)=phi(a)phi(x)
}
}
int n;scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int x;scanf("%d",&x);
printf("%d\n",phi[x]);
}
return 0;
}
兩道水題
法雷級數
題目描述
【題意】
法雷級數Fi的定義如下:
給定一個i(i>=2),那麼所有的 a/b (0<a<b<=i 且 gcd(a,b)==1 )組成了Fi,
例如:
F2 = {1/2}
F3 = {1/3, 1/2, 2/3}
F4 = {1/4, 1/3, 1/2, 2/3, 3/4}
F5 = {1/5, 1/4, 1/3, 2/5, 1/2, 3/5, 2/3, 3/4, 4/5}
你的任務就是統計對於給出的i,Fi集合的元素個數。
【輸入格式】
第一行n(1<=n<=10000)
下來n行,每行一個整數x,表示求Fx的元素個數。(1<=x<=2000 0000)
【輸出格式】
每次詢問輸出一行一個整數,即Fx的元素個數。
【樣例輸入】
4
2
3
4
5
【樣例輸出】
1
3
5
9
我已經不想說什麼了,就是求到的所有函數的值。
#include<cstdio>
#include<cstring>
#define N 21000000
using namespace std;
int ins[2100000],phi[N];
long long sum[N];//要用long long
int main()
{
for(int i=2;i<=20000000;i++)
{
if(!phi[i])ins[++ins[0]]=i,phi[i]=i-1;
for(int j=1;j<=ins[0] && ins[j]*i<=20000000;j++)
{
if(i%ins[j]==0)
{
phi[ins[j]*i]=ins[j]*phi[i];
break;
}
else phi[ins[j]*i]=phi[ins[j]]*phi[i];
}
sum[i]=sum[i-1]+phi[i];//sum統計
}
int n;scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int x;scanf("%d",&x);
printf("%lld\n",sum[x]);
}
return 0;
}
可見點數
【題意】
給出一個正整數N,求在(x,y){0<=x,y<=N}的區域中,
從(0,0)點可以看到的點的個數,
不包括(0,0)自己。
比如上圖就是N=5的圖,0 ≤ x, y ≤ 5
【輸入格式】
輸入有 C (1 ≤ C ≤ 1000) 組數據
每組數據給出一個正整數 N (1 ≤ N ≤ 1000)
【輸出格式】
每組數據輸出三個整數分別是:第幾組,N,和可以看到的點數
【樣例輸入】
4
2
4
5
231
【樣例輸出】
1 2 5
2 4 13
3 5 21
4 231 32549
也是水題一道,我們發現沿着對角線切割發現左邊的可見點數與右邊的可點點數不就是法雷級數嗎?
當然,有三個點需要我們特地加上:
那麼答案就是
#include<cstdio>
#include<cstring>
#define N 1010
using namespace std;
int ins[2100],phi[N];
long long sum[N];
int main()
{
for(int i=2;i<=1000;i++)
{
if(!phi[i])ins[++ins[0]]=i,phi[i]=i-1;
for(int j=1;j<=ins[0] && ins[j]*i<=1000;j++)
{
if(i%ins[j]==0)
{
phi[ins[j]*i]=ins[j]*phi[i];
break;
}
else phi[ins[j]*i]=phi[ins[j]]*phi[i];
}
sum[i]=sum[i-1]+phi[i];
}
int n;scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int x;scanf("%d",&x);
printf("%d %d %lld\n",i,x,sum[x]*2+3);
}
return 0;
}
原根
當,滿足最小的正整數稱爲模的階,很明顯互質纔有階。
原根:當模的階等於時,稱爲的原根。
我們這裏規定原根的數量表示的是到中爲原根的數量。
原根有以下性質:
- 在模的情況下能不重複的形成的所有質因數。(兩個集合的數字都有個)
- 一個數字有原根的必要條件爲,其中爲奇素數,爲正整數。
- 一個數字的原根數量
那麼如何證呢?
歐拉定理
好,讓我們來看看歐拉定理。(霧)
之前跟大家講了會填坑的
我們知道(不互質),但是這個有什麼用?
這個爲階最大爲提供了一個必要的條件。
我們來證證吧。
我們設的質因子爲。
現在有一個與互質,設
- 沒有一個。
這個讓我們用反證法證一證吧:
因爲與互質,那麼整除於,但是,所以不成立。
- 除以得到的餘數與互質。
依然用反證法證一證:
那麼(爲未知整數)。
和不互質,那麼那麼我們可以知道原式可化爲:
那麼後面兩個分數都是整數,那就必須是整數,但是都與互質,所以就與互質,所以不成立。
依據上面我們可以知道集合在的情況下可以全部不重複映射到集合,那麼我們將乘起來得到也就是,因爲都與互質,所以,所以
證畢。
當然,費馬小定理就是歐拉定理的特殊情況,爲素數時,。
原根部分性質證明(數量證不出來,一個還沒填的坑)
注:一下證明中指數都在到
如同歐拉定理,我們先證沒有一個
反證法:
假如有
那麼:
那麼我們知道與互質,所以與互質,而模的階爲,,所以不成立,所以不成立。
再證除以得到的餘數與互質。
反證法:
然後就跟歐拉定理裏面的一樣了。
所以我們就證出了在模的情況下能不重複的形成的所有質因數。(兩個集合的數字都有個)
這個怎麼證?我們知道(爲模的階)
那麼
由於原根的階就是,所以得證。
當然,數量以及是否有原根我不會證。
聽說要羣論,我果然還是太菜了。
擴展:原根的求法
這個就很棒了,我們可以先假設是奇素數,然後求的原根。
以下內容摘自https://oi-wiki.org/math/primitive-root/,OIwiki也十分有名,大家可以上去搜一些OI的東西。
當然,我們還會做一些解釋,其中這是求是奇素數的,當然,可以吧求法中的換成,其他的也換一下也沒有多大問題,依然可以證,不過首先要有原根纔可以用。
圖片中類似的東西表示循環。
這裏的裴蜀定理指的是如果有一個二元一次不定式滿足:()那麼此不定式就有無數個整數解,當然,在上面同餘的時候我們也類似的證了證,那麼也就沒多大問題了,因爲是擴展,就沒有代碼了QAQ。
代碼
題目描述
給出n個正整數,求每個正整數的原根個數。
注意:不是每一個數都有原根!這道題和1159不一樣!
輸入
第一行一個數n
接下來n行,一行一個數xi。
輸出
一行一個數,第i行是xi的原根個數(無原根則輸出0)。
樣例輸入
8
1
2
3
4
8
9
10
18
樣例輸出
0
1
1
1
0
2
2
2
提示
1<n<=10000
1<=xi<=2*10^7
#include<cstdio>
#include<cstring>
#define N 21000000
using namespace std;
int ss[2100000],phi[N];
bool mt[N];
int main()
{
mt[2]=mt[4]=true;phi[1]=1;
for(int i=2;i<=20000000;i++)
{
if(!phi[i])
{
phi[i]=i-1,ss[++ss[0]]=i;
if(i!=2)
{
long long now=i;
while(now<=N)//存在原根的地方
{
mt[now]=true;
now*=i;
}
}
}
for(int j=1;j<=ss[0] && ss[j]*i<=20000000;j++)
{
if(i%ss[j])phi[ss[j]*i]=phi[ss[j]]*phi[i];
else
{
phi[ss[j]*i]=phi[i]*ss[j];
break;
}
}
}
int n;scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int x;scanf("%d",&x);
if(mt[x]==true || (x%2==0 && mt[x/2]==true))printf("%d\n",phi[phi[x]]);//存在原根
else printf("0\n");
}
return 0;
}
高斯消元
題目描述
【題意】
有n個未知數x1,x2,x3……xn,滿足:
a11*x1+a12*x2+a13*x3……+a1n*xn=b1
a21*x1+a22*x2+a23*x3……+a2n*xn=b2
……………………………………
an1*x1+an2*x2+an3*x3……+ann*xn=bn
求x1,x2,x3……xn的值。(保證有解)
【輸入格式】
第一行給出n(1<=n<=100)
下來n行,每行給出n+1個實數,分別是 ai1~ain 和bi。
【輸出格式】
輸出x1,x2,x3……xn的值(相鄰兩個用一個空格隔開,每個數保留3位小數)
【樣例輸入】
3
2.5 5.0 3.0 32.5
1.0 4.5 2.0 22
4.0 3.5 1.5 26.5
【樣例輸出】
3.000 2.000 5.000
【提示】
aij>0
普通
我們小學做元一次方程都是消元的嗎,結果沒想到OI中叫高斯消元,好像很高大尚。
其實就是用式消去到式的第一項,用式消去到式的第二項…
#include<cstdio>
#include<cstring>
#define N 110
using namespace std;
double a[N][N],f[N];
int n;
void guess()
{
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
double bi=a[j][i]/a[i][i];//求出比例
for(int k=n+1;k>=i;k--)
{
a[j][k]-=a[i][k]*bi;//暴力double減
}
}
}
for(int i=n;i>=1;i--)
{
double bi=a[i][n+1];
for(int j=i+1;j<=n;j++)bi-=a[i][j]*f[j];
f[i]=bi/a[i][i];
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
for(int j=0;j<=n;j++)scanf("%lf",&a[i][j+1]);
}
guess();
for(int i=1;i<n;i++)printf("%.3lf ",f[i]);
printf("%.3lf\n",f[n]);
return 0;
}
輾轉相除法
輾轉相除不是嗎?呀。
但是在裏面,小數有時總是這麼不盡任意,雖然有四捨五入,但是整數在調試還是什麼方面都很強!
而且如果要求求的是整數解就很棒棒了,所以在矩陣樹中的消元許多人寧願多個也要打輾轉相除。
複雜度
因爲沒有題目驗證正確性,將在矩陣樹給出輾轉相除版。
先講講:
我們用第行消掉第行的第項時(根據高斯消元我們知道兩行的前項都已經被消掉了),我們不是暴力直接除,而是將第項每一行乘以減到第行,然後交換行,我們會發現其實就是對項做類似輾轉相除的操作然後改變一下其他項,當然,爲了節省時間也可以不交換,不過得用一些其他方法實現類似交換的操作。
矩陣樹與證明
又是一個大的內容。。。
參考資料:
https://www.cnblogs.com/zj75211/p/8039443.html
https://blog.csdn.net/qq_34921856/article/details/79409754
https://www.cnblogs.com/twilight-sx/p/9064208.html
https://blog.csdn.net/a_crazy_czy/article/details/72971868
https://www.cnblogs.com/candy99/p/6420935.html
https://www.cnblogs.com/xzzzh/p/6718926.html
https://baike.baidu.com/item/Binet-Cauchy%E5%AE%9A%E7%90%86/8255247
https://baike.baidu.com/item/%E5%8D%95%E4%BD%8D%E7%9F%A9%E9%98%B5/8540268?fr=aladdin
你看看這麼多,就知道這個有多噁心了!!!!!!!!!!!!!!
未了結的坑
矩陣樹是解決一個圖中生成樹的數量。
目前我只會做無向圖的,而且也只會證無向圖的,當然,按我現在對矩陣樹的理解,有沒有重邊應該也無所謂,大家可以試一試,因爲時間緊迫,所以就不證明了,下面的默認沒有重邊。
因爲老師開始要求學幾何了,就只能拋坑了QAQ
未填完的坑:
- 有向圖的矩陣樹。
- 變元矩陣樹。
- 證明有重邊也無所謂
無向圖
關聯矩陣
在無向圖裏面,關聯矩陣是的一個矩陣(是點數,的邊數),如果第條無向邊是,那麼矩陣裏的點值,的點值。(倒過來也可以,無所謂)
給張圖更好的理解:
我們設無向圖的關聯矩陣爲,轉置矩陣爲
轉置矩陣:
就是把現矩陣的第一行作爲第一列,第一列作爲第一行。
就是轉置矩陣。
矩陣乘法:
這裏放上度孃的解釋https://baike.baidu.com/item/%E7%9F%A9%E9%98%B5%E4%B9%98%E6%B3%95/5446029?fr=aladdin:
Kirchhoff矩陣
這裏只講無向圖的Kirchhoff矩陣。
Kirchhoff矩陣就是,也就是一個的矩陣
所以我們可以知道,Kirchhoff矩陣的項就是的第行與第行的內積。
當時,如果有,那麼說明第條邊不是的。相反,就代表有一條邊,所以我們知道,Kirchhoff矩陣的項表示的是邊的個數的相反數,目前只能是。
當時, 如果有,那麼說明他不在第條邊上,相反就是在,所以Kirchhoff矩陣的項表示的是的度數。
當然,還有個簡單的構建方法,就是(度數矩陣 - 鄰接矩陣)。
爲什麼要將關聯矩陣,證明的時候就知了。
我們容易知道這個矩陣的每個行和每個列的和爲。
因爲度數爲正,其他數爲負,這一行(列)的所有負數的和就是度數的相反數。
行列式
對於一格的行列式:
行列式的值爲:,其中爲矩陣的第行第列。
而是長度爲的任意一個全排列,然後全排列的個數有,然後表示的是的逆序對數(就是一對逆序對)。
其實就是選個點,行和列不重複
比如:
的矩陣
行列式有一些的性質。
定理1:與的轉置矩陣的行列式相等。
我們發現轉置後點的相對位置不變,第行變成第列,第列變成了第行,本質沒變,所以行列式相等。
定理2:當某行(列)的值全部爲0,那麼這個矩陣的行列式爲。
怎麼選點都有個,證畢。
定理3:某一行與某一行交換,行列式取負。
證明:
中,現將行互換。
設從到,有個數字的(也就是列數)小於,有個大於,有個數字小於,有個大於,,不可能有相等的。
那麼交換前的貢獻有對,交換後有
那麼兩個相減爲:,而卻與交換的又會加或減,所以取反。
交換列其實也是一樣的。
定理4:有兩行(列)相等,行列式值爲
交換兩列後的行列式取反,但是我們發現兩次的行列式是相等的,所以,,行列式的值爲。
定理5:當有一行有公因子時,可以把公因子提出(相反,外面有個係數,乘到裏面某一行(列)也是可以的)。
定理6:當一行(列)爲另一行(列)的比例時,行列式的值爲。
將這一行的因子提出,和另一行相等,行列式的值爲。
定理7:某一行(列)的數字依次加上一些數字,那麼加上後的行列式可以拆爲兩個行列式相加:
如:矩陣爲,爲,爲,那麼
其實很簡單證,原本每個式子爲:,現在爲根據乘法分配率,這是可以拆開的。
定理8:某一行加上另爲一行的倍數,行列式的值不變。
很簡單,現在的行列式拆成原來的和某個奇怪的,某個奇怪的行列式存在一行是另一行的比例關係,所以這個行列式爲,不影響值。
通過定理8,我們發現可以對行列式進行消元,類似高斯消元一樣,消成類似的樣子
我們管這個矩陣叫上三角矩陣,很明顯,行列式的值就是對角線的值。
求法
其實生成樹的個數就是Kirchhoff矩陣的階代數餘子式的行列式值結果。
一個矩陣(長寬相等)的階餘子式刪去第行與第列。
假設我們又乘上了
就是階的代數餘子式,我們可以化簡一下:
Kirchhoff矩陣對於行列式又有一些性質:
定理1:Kirchhoff矩陣的兩個不同的階代數餘子式的行列式相等。
我們設兩個餘子式刪的行是同一行,不同列(可以類似的推出不同行,同列,然後得證)
兩個矩陣爲(刪去第行,第或列)。
我們先將原本的矩陣變換一下,第列取反,然後加去除列的其他列乘上的值,因爲每一行的值爲,那麼第列的數字就等於減去除列的其他所有數字,也就是第列的數字,然後我們將這一列在中一直移到列(也就是中列的位置),要交換次,那麼這個交換過程就乘上次,然後,所以我們就將轉換成,且並沒改變值,得證。
定理2:Kirchhoff矩陣的行列式的值爲。
因爲每一行的和爲0,在形成上三角的過程中,最後一行消掉和個,,但是最後一行的和爲,所以個都是。
代碼
我們默認去掉第行第列。
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#define N 30
using namespace std;
typedef long long LL;
double a[N][N],hehe=1e-8/*精度交換*/;
int n,m;
void zwap(double &x,double &y){double z=x;x=y;y=z;}
double guess()
{
for(int i=1;i<=n;i++)
{
int what=i;//前i-1列不能交換
while(fabs(a[what][i])<=hehe)what++;
if(i==n+1)return 0;//沒辦法
for(int j=1;j<=n;j++)zwap(a[what][j],a[i][j]);
for(int j=i+1;j<=n;j++)
{
double bi=a[j][i]/a[i][i];
for(int k=n+1;k>=i;k--)a[j][k]-=a[i][k]*bi;
}
}
double ans=a[1][1];
for(int i=2;i<=n;i++)ans=ans*a[i][i];
return fabs(ans);
}
int main()
{
scanf("%d%d",&n,&m);n--;
for(int i=1;i<=m;i++)
{
int x,y;scanf("%d%d",&x,&y);
a[x][x]+=1;a[y][y]+=1;
a[x][y]-=1;a[y][x]-=1;
}
printf("%.0lf",guess());
return 0;
}
輾轉相除法:
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#define N 30
using namespace std;
typedef long long LL;
LL a[N][N];
int n,m;
void zwap1(int &x,int &y){int z=x;x=y;y=z;}
void zwap2(LL &x,LL &y){LL z=x;x=y;y=z;}
LL guess()
{
int now,cnt;
for(int i=1;i<=n;i++)
{
int what=i;
while(a[what][i]==0)what++;
if(i==n+1)return 0;
for(int j=1;j<=n;j++)zwap2(a[what][j],a[i][j]);
for(int j=i+1;j<=n;j++)
{
now=i,cnt=j;//傳說中免交換神器
while(a[now][i])
{
LL bi=a[cnt][i]/a[now][i];//輾轉相處
for(int k=i;k<=n;k++)a[cnt][k]-=a[now][k]*bi;
zwap1(now,cnt);
}
if(cnt!=i)for(int k=i;k<=n;k++)zwap2(a[j][k],a[i][k]);//交換
}
}
LL ans=a[1][1];
for(int i=2;i<=n;i++)ans=ans*a[i][i];
return abs(ans);
}
int main()
{
scanf("%d%d",&n,&m);n--;
for(int i=1;i<=m;i++)
{
int x,y;scanf("%d%d",&x,&y);
a[x][x]++;a[y][y]++;
a[x][y]--;a[y][x]--;
}
printf("%lld\n",guess());
return 0;
}
證明
根據柯西-比內公式(後面會有比較垃圾的證明)可得:
表示的是,代表在中隨便選個不重複數字的集合,表示所有的集合,就是行選列的一個的集合,選列,那麼我們會發現選行不就是找到條邊?
討論情況:
- 如果有幾條邊構成一個環,那麼這個爲。
我們一步步把這幾行幾列換到一起,組成一個類似的小矩陣,因爲每條邊都只有兩個數字,所以這列這個行列式的這列只能在這一選一個數字來乘,所以我們可以認爲這個矩陣的行列式等於這個矩陣去掉這三列的行列式乘以這個小矩陣的行列式,正負先不管,大家可以畫個圖模擬一下,因爲這三行這三列只能在這個小矩陣裏面選。
但是這個小矩陣我們消列,變成下三角,(),我們發現先用第一列消最後一列,最後一列第二個又有數字,就用第二列消,也就是依次用第一列一直下去消,那麼我們模擬一下過程,當第一行消最後一列時,用消時,最後一列加上第一列的取反,那麼最後一列第二個元素爲,如果用消,直接加,那麼最後一列的第二個數字等於,怎麼這麼巧,消完後最後一列的第二個元素就等於第一個?其實仔細想想就知道,就是是從第二列開始去消也是,那麼最終會右下角的倒數兩行兩列就變成了,那麼最後一列就是倒數第二列的倍,行列式的值爲。
- 沒有環爲
我們已經把第行消掉,又沒有環,必然有一條邊是連向的,那麼我們這一列選的就是這條邊的另外一個端點,但是個點的圖邊不就是棵樹嗎,而且一邊佔一列,我們可以認爲一條邊選一個點且選的點不重複,那麼就是在列裏面選個點。
我們會發現這個端點就是另外一些邊的端點,那麼這些邊又只能選另外一個端點,以此類推,這個行列式的有效選點只有一種,所以爲,再取個平方就是
通過上面我們會發現Kirchhoff矩陣的階代數餘子式的行列式值就是生成樹的個數!
柯西-比內公式
我是看度娘證明的,很不錯https://baike.baidu.com/item/Binet-Cauchy%E5%AE%9A%E7%90%86/8255247。
當然,參考鏈接有,裏面提到的Laplace展開定理我也不會,但是其中用到Laplace展開定理的地方我們都可以感性的理解。。。
我就是對它的一個補充說明
提一下,就是柯西比內公式講了
放上圖片,然後一一解釋:
一個的單位矩陣爲,也就是對角線爲,單位矩陣乘以任何矩陣都等於矩陣本身。
我們把M的第n+1,n+2…n+s行的第 倍加到第k行去.(k=1,2…n),也就是用將的位置全部變爲0,然後原本的位置加上變成了,並且行列式的值並沒變。
下面的證明不理先
因爲前面的行只能選中的數字,且爲,爲,所以裏面的數字也不能選,只能選裏面的數字。
那麼
對進行分解,前行只能選中的數字,當時,選行必定有些選不到裏面選到了,所以爲,如果,那麼就是隻能選裏面的數字了,也就是,但是時,我們就只能選列,那麼我們可以相應的知道中只能選主對角線上(從左上到右下的對角線)的點,也就是(類似行等於列),那麼也就只能選其他行的個點,那麼也就是我們選的那些列的列數,也就得到了我們柯西-比內的基本結構了,但是我們並沒有理正負。
當時
的正負數:
,而且中的個點每個點都與中的個點組成逆序對數,也就是,所以在理正負的情況下爲。
分解後(因爲那個公式實在太難寫了QAQ)的正負號:
與的數字構不成逆序對數,那麼我們考慮的,我們設選的的列數爲,那麼選的行數就爲
我們設現在選的其中一個主對角線的點的行列爲,那麼再看我們在選的個點,中的點的行數皆小於,當時()有逆序對,中的點列數皆小於,當時有逆序對,拆開爲,發現當和都有一個逆序對,那麼中每個點都有個逆序對,也就是個,自帶個,那麼就是
證明的奇偶性相等。
當爲奇數,得證QMQ。
當爲偶數,爲奇數偶數時,與的奇偶都是一樣的。
那麼也就是相等,所以柯西-比內公式得證。
不會Laplace展開定理就是煩QAQ。
小結
還是好多坑沒填QAQ。