基礎數論總結
- 2019 年 10 月 6 日
- 筆記

快速冪

非遞歸版
import java.util.Scanner; public class Main { public static void main(String[] args) { // TODO 自動生成的方法存根 Scanner sc=new Scanner(System.in); int t=sc.nextInt(); for(int i=0;i<t;i++) { long b=sc.nextLong(); long c=1000000007;long a=2; long res = 1; a %= c; for (; b != 0; b /= 2) { if (b % 2 == 1) res = (res * a) % c; a = (a * a) % c; } System.out.println(res); } } }
遞歸版(用的比較多)
import java.util.Scanner; public class Main { static long c=1000000007; public static void main(String[] args) { Scanner sc = new Scanner(System.in); int t=sc.nextInt(); for(int i=0;i<t;i++) { long a=2; long b=sc.nextLong(); long value=divide((long)2,b); System.out.println(value%c); } } private static long divide(long a,long b) { if(b==0)return 1; else if(b%2==0) {return divide((a%c)*(a%c),b/2)%c;} else return a%c*divide((a%c)*(a%c),(b-1)/2)%c; } }
矩陣快速冪
題目鏈接 核心思想為:

從右往左。可以一直遞推,然後到最後一項,然後快速冪求矩陣,矩陣最終的結果就是所求結果。更新:java的矩陣通用乘法可以表示為,可以將下列程式碼替換道ac程式碼中:
static int [][] multiplication(int a[][],int b[][]){// int x=a.length;//a[0].length=b.length 為滿足條件 int y=b[0].length;//確定每一排有幾個 int c[][]=new int [2][2]; for(int i=0;i<x;i++) for(int j=0;j<y;j++) { //需要確定每一個元素 //c[i][j]; for(int t=0;t<b.length;t++) { c[i][j]+=(a[i][t]%10000)*(b[t][j]%10000); c[i][j]%=10000; } } return c; }
import java.util.Scanner; public class Main { public static void main(String[] args) { // TODO 自動生成的方法存根 Scanner sc=new Scanner(System.in); while(sc.hasNext()) { int n=sc.nextInt(); if(n==-1)break; if(n==0) {System.out.println(0);} else { n-=1;// int a[][]= {{1,1},{1,0}}; int b[][]={{1,0},{0,1}};//單位矩陣 int time=0; while(n>0) { if(n%2==1) { b=q(a, b); } a=q(a, a);n/=2; } System.out.println((b[0][0])); } } } static int [][] q(int a[][],int b[][]){// int value1=a[0][0]*b[0][0]%10000+a[0][1]*b[1][0]%10000;//左上 int value2=a[0][0]*b[0][1]%10000+a[0][1]*b[1][1]%10000;//左上 int value3=a[1][0]*b[0][0]%10000+a[1][1]*b[1][0]%10000;//左上 int value4=a[1][0]*b[0][1]%10000+a[1][1]*b[1][1]%10000;//左上 int c[][]=new int [2][2]; c[0][0]=value1%10000; c[0][1]=value2%10000; c[1][0]=value3%10000; c[1][1]=value4%10000; return c; } }
拓展歐幾里得
拓展歐幾里得模板 參考:哈爾濱理工大學ACM培訓資料彙編/ACM-ICPC培訓資料彙編* 基本原理 :設 a 和 b 不全為 0,==則存在整數 x,y 使得 xa+yb=gcd(a,b)=c== 對於輾轉相除法的最後一項 此時 b=0,則 gcd(a,b)=1a+0b,(這個a,b是經過gcd的最後一項a,b)
因為gcd(a,b)=gcd(b,a%b)
則有x *a+y *b=x1 *b+y1 * (a%b) 將等式右邊變形,
b *x1+(a%b) *y1=b *x1+(a-(a/b) *b) *y1=a *y1+b *(x1-(a/b) *y1)
則,x=y1,y=x1-(a/b) *y1 則可由後向前迭代得到 x,y
解題思路 :
對於擴展歐幾里德定理的題,一般都需要進行一定的推導之後得到一個形式為 xa+yb=c 的方程,然後根據 c 確定解是否存在, 如果 c 可以被 gcd(a,b)整除,那麼方程有 解,否則方程無解。 而且所得的解是不唯一的,對於一組解 x0,y0 則其所有解可以表示為 x=x0+b/gcd(a,b)*t,y=y0-a/gcd(a,b)*t,t=0,+1,+2……一般會要求找出 x 或者 y 的最小正整數 解,這個時候需要做一些調整。
總的來說。遞歸是一來一回的過程,在gcd中,我們只用到了去的過程,求的最大公約數,而在exgcd中,我們發現了在來的過程中,某些數按照一定的規則變化,可以得到我們想要的結果而已。
static long x=0,y=0; ... static long extgcd(long a,long b) { if(b==0) {x=1;y=0;return a;} long ans=extgcd(b, a%b); long team=x; x=y; y=team-a/b*y; return ans; }
求逆元: 從拓展歐幾里得中知道可以求ax+by=c,一般是解決這類問題
當a,b互質時候。c一定等於1.因為gcd(a,b)=1,c必須被gcd整除,那麼如果有解一定是1.
那麼ax+by=1.
求逆元一般形式(求a關於1mod m的逆元)
ax≡1 (mod m). x就是所求的逆元
變形為 ax+bm=1
這樣就可以運用拓展歐幾里得(但是x要大於0處理)
static long x=0;static long y=0;//全局變數 ...... long q=tgcd(a,b); // long x1=x; if(1%q!=0) {out.println("Not Exist");} else { while(x<=0){//x*a+y*b=1 要求x>0這樣並且要求x最小,那麼這樣操作就相當於+ab-ab操作。剛開始還沒明白 x+=b; y-=a; } system.out.println(x);}// static long tgcd(long a,long b) { if(b==0) {x=1;y=0;return a;} long ans=tgcd(b,a%b); long team=x; x=y; y=team-a/b*y; // System.out.println(x); return ans; }
還有用小費馬可以求逆元,就不介紹了。
例題
杭電2669 給a,b求Xa + Yb = 1.如果沒有則輸出sorry。 可以通過拓展歐幾里得指導Xa + Yb = gcd(a,b). 不言而喻要判斷gcd(a,b)是否等於1.如果不等於1,那麼就是sorry。如果等於一,那麼還不能讓x小於0,要對x,y進行加減操作滿足x>0;拓展歐幾里得是通過遞歸從下往上進行運算。
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.StreamTokenizer; /* * 拓展歐幾里得 */ public class hdu2669 { static long x=0;static long y=0; public static void main(String[] args) throws IOException { // TODO 自動生成的方法存根 StreamTokenizer in=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in))); PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); while(in.nextToken()!=StreamTokenizer.TT_EOF) { long a=(long)in.nval; in.nextToken(); long b=(long)in.nval; long q=tgcd(a,b); if(1%q!=0) {out.println("sorry");}//gcd要和要求相等(這裡等於1) else { while(x<=0){//x*a+y*b=1 要求x>0這樣並且要求x最小,那麼這樣操作就相當於+ab-ab操作。剛開始還沒明白 x+=b; y-=a; } out.println(x+" "+y);}// out.flush(); } } static long tgcd(long a,long b) { if(b==0) {x=1;y=0;return a;} long ans=tgcd(b,a%b); long team=x; x=y; y=team-a/b*y; return ans; } }
題目:
兩隻青蛙在網上相識了,它們聊得很開心,於是覺得很有必要見一面。它們很高興地發現它們住在同一條緯度線上,於是它們約定各自朝西跳,直到碰面為止。可是它們出發之前忘記了一件很重要的事情,既沒有問清楚對方的特徵,也沒有約定見面的具體位置。不過青蛙們都是很樂觀的,它們覺得只要一直朝著某個方向跳下去,總能碰到對方的。但是除非這兩隻青蛙在同一時間跳到同一點上,不然是永遠都不可能碰面的。為了幫助這兩隻樂觀的青蛙,你被要求寫一個程式來判斷這兩隻青蛙是否能夠碰面,會在什麼時候碰面。
我們把這兩隻青蛙分別叫做==青蛙A和青蛙B==,並且規定緯度線上東經0度處為原點,由東往西為正方向,單位長度1米,這樣我們就得到了一條首尾相接的數軸。設青蛙A的出發點坐標是x,青蛙B的出發點坐標是y。青蛙A一次能跳m米,青蛙B一次能跳n米,兩隻青蛙跳一次所花費的時間相同。緯度線總長L米。現在要你求出它們跳了幾次以後才會碰面。 Input
輸入只包括一行5個整數x,y,m,n,L,其中x≠y < 2000000000,0 < m、n < 2000000000,0 < L < 2100000000。 Output 輸出碰面所需要的跳躍次數,如果永遠不可能碰面則輸出一行"Impossible" Sample Input 1 2 3 4 5 Sample Output 4
分析
拓展歐幾里得以前記過
分析一下設見面時間為t。青蛙A的位置是x+mt;青蛙B的位置是y+nt;兩隻青蛙項目,那麼說明兩隻青蛙之間要麼誰比誰多整數圈數。那麼就得到x+mt-(y+nt)=k*L.變形一下(m-n)t-kL=(y-x).這就是m-n,L,y-x分別已知,而t和k未知,求最少的t.(t越少跳的越少。)
那麼設m-n=a,L=b,t=X,-k=Y,y-x=c.原始就是aX+bY=c,其中a,b,c已知,求X最小正整數。你要判斷c是否與gcd(a,b)互質,如果不互質則沒有結果。
接著你求出的初始X是gcd(a,b)的情況,你要判斷c是否是gcd的倍數,如果跟他互質,那麼涼涼,不可能遇到,不滿足extgcd的條件,如果是倍數。假設n倍,你可以你可以nXa,nYa,c相當於同時擴大倍的一種解,但是這不一定是最優解,你需要根據實際加減操作找到最小正解!
ac程式碼
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.StreamTokenizer; public class poj1061 { static long X=0; static long Y=0; public static void main(String[] args) throws IOException { // TODO 自動生成的方法存根 StreamTokenizer in=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in))); PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); in.nextToken();long x=(long)in.nval;//A起始位置 in.nextToken();long y=(long)in.nval;//B起始位置 in.nextToken();long m=(long)in.nval;//A的速率 in.nextToken();long n=(long)in.nval;//B的速率 in.nextToken();long L=(long)in.nval;//長度 long a=m-n; long c=y-x; long b=L; if(a<0) {a=-a;c=-c;} long res=extgcd(a,b);// res=gcd(a,b) //c必須是res的倍數,如果互質的話就不滿足拓展歐幾里得的方程式,而對應的結果首先要跟著倍數擴大 if(c%res!=0) {out.println("Impossible");} else { /* * 可能難理解一點 * x=x0+(b/gcd(a,b))*t * x=x0+(b/res)*t找到最小的正整數x,那麼就是x%(b/res)了,如果小於0就是(x%b/res)+b/res了 */ X=X*(c/res); long t=b/res; if(X>=0) X=X%t; else X=X%t+t; out.println(X); } out.flush(); } private static long extgcd(long a, long b) { if(b==0) { X=1;Y=0; return a; } long res=extgcd(b, a%b); long team=X; X=Y; Y=team-(a/b)*Y; return res; } }
github地址
素數篩
在判定素數的問題中,隨著不斷學習,裡面的拓展性也在不斷地增加。
問題:判定一個數是否為素數:
想都不想的方法:
static boolean isprime(int value){ for(int i=2;i<value;i++) { if(value%i==0) {return false;} } return true; }
暴力從開始判斷的方法。複雜度O(n)。當數據大於1e7左右基本就爆了。更別說多組輸入了
稍微思考數據組成
對於一個數n如果==不是素數==,一定有a*b=n(a<b);a<根號n,b>根號n,所以只要出現一定是成雙成對。所以你只要==找到1個==就能說明這個數不是素數。所以從開始遍歷到a是最省時的方法。因為a是log級。演算法複雜度為O(logn)所以程式碼為:
static boolean isprime(int value) { for(int i=2;i*i<value+1;i++) { if(value%i==0) {return false;} } return true; }
這種情況能夠基本單輸入的素數問題。但是遇到多個輸入肯定也會GG的。
埃拉托斯特尼(Eratosthenes)篩法
問題:多個輸入,問關於素數相關的問題。 如果用上述方法肯定爆。多組輸入的最好解決辦法是打表。至於打表,如果上述的打表nlogn打表的話會TLE,所以就要換一種思考方式。
埃氏篩的核心思想就是==將素數的倍數確定為合數==。對於一個從2開始連續數據,我們假設其中每一個都是素數。如果一個數為素數,那麼這個數的倍數一定也是素數。所以演算法大致流程: 2: [i=(2+2)—>(+2)數組尾],4,6,8,10 * * 不是素數 3: [i=(3+3)—>(+3)數組尾],6,9,12 * * 不是素數 4: [i=4]不是素數,跳過 5: . . . . . . . . . . . . 這個到除了前面幾次的計算量多一點,到後面隨著數據增大對整個複雜度相加是比較小的,演算法複雜度為O(nloglogn);別小瞧多的這個logn,數據量大一個log可能少不少個0,那時間也是十倍百倍甚至更多的差距。 具體程式碼為
static boolean isprime[]; static long prime[]; static void getprime() { prime=new long[100001];//記錄第幾個prime int index=0; isprime=new boolean [1000001]; for(int i=2;i<1000001;i++) { if(!isprime[i]) { prime[index++]=i; } for(int j=i+i;j<1000000;j=j+i)//他的所有倍數都over { isprime[j]=true; } } }
歐拉篩——線性篩
對於上述的埃氏篩,雖然能解決大部分的問題。但是不難發現裡面還是有挺多不夠精簡的地方,比如有的地方會重複訪問。而歐拉篩在埃氏篩的基礎上進行優化,達到O(n)的複雜度。
static boolean isprime[]; static int prime[]; static void getprimeoula()// 歐拉篩 { prime = new int[100001];// 記錄第幾個prime int index = 0; isprime = new boolean[1000001]; for (int i = 2; i < 1000001; i++) { if (!isprime[i]) { prime[index++] = i; } for (int j = 0; j < index && i * prime[j] <= 100000; j++){ isprime[i * prime[j]] = true;// if (i % prime[j] == 0) break; } } }
唯一分解定理
算數分解定理(唯一分解定理):
算數分解定理(唯一分解定理):
定義:
任何一個大於1的自然數 N,如果N不為質數,那麼N可以唯一分解成有限個質數的乘積N=P1a1 P2a2P3a3…Pnan,這裡P1<P2<P3…<Pn均為質數,其中指數ai是正整數。這樣的分解稱為 N 的標準分解式.
應用:對於一個正整數n,如果n=q1a1 * q2 a2 * …* qnan,那麼他的正因數個數為(1+a1) * (1+a2)* . . . *(1+an);
樣例:
1000=2* 500=2* (2 * 250)=2 * 2 *( 2 * 125)=2 * 2 * 2 *(5 * 25)=2 * 2 * 2 * 5 * (5 *5)=23*53.可以看的出來基本策略就是從2開始除,直到不是2的倍數然後網上遞增求。
計算方法:
計算n的分解方式。主要是通過數的自身對從最小的質數開始整除除一直到不能整除,直到跳出限制條件。
- 你可以從2到n;逐個遍歷判斷,滿足條件的話就在數組中添加對應的count。當然,每被計算一次的時候,這個數就要被除一次。
- 上面方法對於大的數據顯然複雜度太高。其實你可以從2遍歷到根號n+1(i * i<n+1)停止,因為最大的理想乘積是i*i=n,其他的數據都是一個左,一個右面。最終分解的時候如果都分到這步了說明要麼後面不剩,要麼就是剩一個大質數。
- 上面雖然從數的量級減少了不少,但是會遍歷很多沒用的合數,比如遍歷過2所有而的倍數都不需要遍歷判斷,所以我們只需要遍歷素數。素數打表遍歷素數,當遇到多組輸入,數據要求較高的時候,先用素數篩打表,把素數預處理,然後直接從2-素數數組中遍歷即可,因為如果一個數能被拆,那麼他如果不能被拆,他就是素數,如果它還能被拆,那麼它就是合數。所以一個數被分解到最後都是素數的次冪相乘!很重要!這樣能夠省的更多的時間。可以參考素數篩模板。
核心程式碼: 不打表:
for(int j=2;j*j<n+1;j++) { while(n%j==0) { n/=j; //根據需求操作 } //根據需求操作... }
打表:
static int index=0;//根據數據範圍範圍內素數的最後一個位置prime[index] long prime[]=new long[100001]; boolean isprime[]=new boolean[1000001]; for(int i=2;i<1000001;i++)//埃拉托色尼篩選法 { if(!isprime[i]) { prime[index++]=i; for(int j=i+i;j<1000000;j=j+i) { isprime[j]=true; } } } ........ ........ // int n; for(int j=0;j<index;j++) { if(n==0||prime[j]>n) {break;}//素數已經超出無法最大的數,退出 long team=0;//其他操作,可以是你自己的邏輯 while(c%prime[j]==0) { c/=prime[j]; team++;//其他操作 } number*=(1+team);//其他操作 }
唯一分解定義有什麼用?
例如給一個1000,這樣的數,問有多少種可能組合使得a * b=1000.或者求a*b中在那個範圍內a,b的排列次數。
首先要了解一個唯一分解定理的應用:對於一個正整數n,如果n=q1a1 * q2 a2 * …* qnan,那麼他的正因數個數為(1+a1) * (1+a2)…(1+an);對於這個,就可以衍生其他問題,比如找兩個因數的組合情況,可能性,在那個範圍等等。其實這就是一個組合問題。對於每一個數有t個,能夠影響最終結果的就是這個素數出現的次數。如果細看雖然每個數的概率都是可能出現和不出現的1/2.但是對於最終結果就是:出現0次,出現1次,出現。。。,出現t次。所以這個數對結果出現的可能行變成了原來次數*(1+t).以此類推,便可得到所有的因數可能的結果。
就例如1000=23 * 53: 對於結果首先2和5是獨立互不影響的。所以對於一個因數。質數2的搭配有四種,出現0個,1個,2個或3個。同理質數5的搭配也是4種,所以最終因數可能出現的次數是4 * 4=1*(3+1)*(3+1)=16個。
### 例題 看下[hdu5248](http://acm.hdu.edu.cn/showproblem.php?pid=5428)
題意:給出n個數,求這n個數中最小的兩個因數乘積,題意有些小的歧義不太好理解。說明白了就是讓你從n個數分解找最小的兩個因子相乘.(1不滿足因為1沒有2個及以上因子).
思路:數據量不大,可以不打表直接素數分解。其實每個數找到2個因子就可以停止了,放到list或者數組中,最後排序判斷因子是否大於等於2個。按照要求輸出
附上ac程式碼:
import java.util.ArrayList; import java.util.List; import java.util.Scanner; public class hdu5248 { public static void main(String[] args) { Scanner sc=new Scanner(System.in); int T=sc.nextInt(); for(int q=0;q<T;q++) { int n=sc.nextInt(); List<Long>list=new ArrayList<Long>(); for(int i=0;i<n;i++) { long te=sc.nextLong(); int num=0; for(int j=2;(long)j*j<te+1;j++) { while(te%j==0) { list.add((long) j); te/=j; num++; } if(num>2)break;//其實找到兩個就不用找了 } if(te>1) {list.add(te);} } if(list.size()<2) System.out.println(-1); else { list.sort(null); System.out.println((long)(list.get(0)*list.get(1))); } } } }
lightoj1341 題目大意:給個面積s。一個邊b,問最小邊大於b的所有可能情況。 思路:整體-多餘。先求出所有的排列次數,然後除以二(要求組合隊數)。再從0頭到b開始剪掉多餘的情況。不需要考慮特大的那邊,因為是對稱的已經除以過二了。 ac程式碼:
埃氏篩
import java.util.Scanner; public class testC { static int index=0; public static void main(String[] args) { // TODO 自動生成的方法存根 long prime[]=new long[100001]; boolean isprime[]=new boolean[1000001]; for(int i=2;i<1000001;i++)//埃拉托色尼篩選法 { if(!isprime[i]) { prime[index++]=i; for(int j=i+i;j<1000000;j=j+i) { isprime[j]=true; } } } Scanner sc=new Scanner(System.in); int t=sc.nextInt(); for(int i=0;i<t;i++) { long a=sc.nextLong(); long b=sc.nextLong(); long number=1; long c=a; if(b*b>=a) {number=0;}//不可能的情況,最小邊大於可能拼成的情況 else { for(int j=0;j<index;j++) { if(c==0||prime[j]>c) {break;}//超出界,跳出 long team=0; while(c%prime[j]==0) { c/=prime[j];team++; } number*=(1+team);//計算 } if(c>1)number*=2; number/=2; //這裡別被繞進去。這裡不是按照次冪計算的,而是按照實打實的一個一個數判斷的。 for(int j=1;j<b;j++) { if(a%j==0)number--; } } System.out.println("Case "+(i+1)+": "+number); } } }
歐拉篩:
import java.util.Scanner; public class testC { static int index=0; public static void main(String[] args) { // TODO 自動生成的方法存根 int prime[] = new int[100001];// 記錄第幾個prime int index = 0; boolean isprime[] = new boolean[1000001]; for (int i = 2; i < 1000001; i++) { if (!isprime[i]) { prime[index++] = i; } for (int j = 0; j < index && i * prime[j] < 1000001; j++) { isprime[i * prime[j]] = true;// 找到的素數的倍數不訪問 if (i % prime[j] == 0) break;// 關鍵!!!! } } Scanner sc=new Scanner(System.in); int t=sc.nextInt(); for(int i=0;i<t;i++) { long a=sc.nextLong(); long b=sc.nextLong(); long number=1; long c=a; if(b*b>=a) {number=0;}//不可能的情況,最小邊大於可能拼成的情況 else { for(int j=0;j<index;j++) { if(c==0||prime[j]>c) {break;} long team=0; while(c%prime[j]==0) { c/=prime[j];team++; } number*=(1+team); } if(c>1)number*=2; number/=2; for(int j=1;j<b;j++) { if(a%j==0)number--; } } System.out.println("Case "+(i+1)+": "+number); } } }
歐拉函數
求歐拉函數程式碼的方式:直接求和打表 歐拉函數的作用:一個數n,求小於n的互質的個數。特例:1——oula(1)=1; 歐拉函數的公式:

其中i為x所有質因數。注意:每種質因數只一個 為什麼會這樣?首先,互質的兩個數一定不能有公共因數。比如9和7互質,9和12不互質,因為有共同因數3.
那麼我難道需要一個個循環比較嗎?
答案先然不可能,因為如果數值過大這是個很大的複雜度。那麼我該如何處理?
換一種思維。比如求24中的互質個數。答案是1,5,7,11,13,17,19,23。共8個
- 24=2 * 2 * 2 * 3;那麼在小於12中的數的核心共同質數為2的倍數或者三的倍數。有人可能說明明還要4,6的倍數,那是因為這些倍數囊括在2,3之中。所以我們每個質因數只記錄一個。
- 看在24中,有1/2的是2的倍數,也就是1/2的數是和24有共同因數2.那麼就有(1-1/2)的數和24沒有共同因數2;
- ;
- 同理那麼就有1/3的數和24有共同因數3,並且(1-1/3)=2/3的數沒有共同因數3.那麼沒有因數2和因數三剩下的不就是和24互質么,那麼概率=(1-1/2) *乘以 (1-1/3)=1/3.總個數為24 *乘以 1/3=8滿足要求。
- 2. 如果一個因數出現多次怎麼排除呢。或者怎麼防止4,6這些數被計入因數中,這就要用到**質因數分解的思想**。只不過我們不需要這個冪出現的次數,只需要讓剩餘的不可能在存在當前這個數為因數的可能性。
附上直接求程式碼:
private static int oula(int team) { int i=0;int res=team;int team1=team; for(i=2;i<(int)Math.sqrt(team1)+1;i++) { if(team%i==0) { res=res/i*(i-1); while(team%i==0) {team/=i;}//保證沒有i作為因子 } } if(team>1)res=res/team*(team-1);//說明後面還有一個比較大的互質因數大小為team return res; }
其中,res為結果,team1作為求因數用。如果實在不能理解,好好看下質因數分解。
打表程式碼:
int a[]=new int[1000001]; for(int i=1;i<1000001;i++) { a[i]=i; } for(int i=2;i+2<1000001;i+=2) { a[i]/=2; } for(int i=3;i+2<1000001;i+=2) { if(a[i]==i) { for(int j=i;j+i<=1000001;j+=i) { a[j]=a[j]/i*(i-1); } } }
例題
歐拉函數/素數判定
題目鏈接
題目
Bamboo Pole-vault是Xzhiland的一項大受歡迎的運動。 Phi-shoe大師是他成功的非常受歡迎的教練。他需要為他的學生提供一些竹子,所以他讓他的助手Bi-Shoe去市場購買。市場上有很多可能的整數長度的Bamboos(是的!)。根據Xzhila的傳統,
竹子的分數=Φ(竹子的長度)
(Xzhilans非常喜歡數論)。對於您的資訊,Φ(n)=小於n的數字,它們相對於素數(除了1之外沒有公約數)到n。因此,長度為9的竹子的得分為6,因為1,2,4,5,7,8是9的相對素數。
助理雙鞋必須為每個學生買一個竹子。作為一個扭曲,Phi-shoe的每個撐桿跳學生都有一個幸運數字。 Bi-shoe希望購買竹子,這樣他們每個人都會得到一張分數大於或等於他/她的幸運數字的竹子。 Bi-shoe希望最大限度地減少購買竹子所花費的總金額。一個竹子單位花費1 Xukha。幫助他
輸入 輸入以整數T(≤100)開始,表示測試用例的數量。
每個案例都以包含整數n(1≤n≤10000)的行開頭,表示Phi-shoe的學生人數。下一行包含n個空格分隔的整數,表示學生的幸運數字。每個幸運數字將位於[1,106]範圍內。
輸出 對於每種情況,列印案例編號和購買竹子所花費的最少金額。有關詳細資訊,請參閱示例 Sample Input 3 5 1 2 3 4 5 6 10 11 12 13 14 15 2 1 1 Sample Output Case 1: 22 Xukha Case 2: 88 Xukha Case 3: 4 Xukha
題意
題意:給你n個整數,第i個整數為Xi。定義phi(k)為k的歐拉函數值,設pi為滿足phi(pi)>=Xi的最小整數,題目就是要求sum(p1,p2,p3,…,pn)。
告訴你幸運數字x,你找出phi(n)=x的這個最小的n,若干個這樣數的合。
首先要清楚幾個概念phi(n)=n-1,==n為素數==時候。因為n和小於它的任意都互質。 所以解題思路大致有兩個:
歐拉函數的角度:
歐拉是最明顯的,要找出大於這個數最小的那個phi[i],如果==單個歐拉函數求會TL==所以需要歐拉打表。沒輸入一個數網上找幾個就行了
素數角度
n為素數時候,phi(n)=n-1,所以第一個phi(i)=t的那個i就是在t右側的第一個素數。有了這個思路你就可以用素數解決問題,可以用素數篩。用直接的素數判定也能過。 c++程式碼: 直接判定
#include <iostream> #include<stdio.h> using namespace std; #define ll long long bool isprime(int index) { if(index<=2)return true; else { for(int i=2;i*i<index+1;i++) { if(index%i==0)return false; } return true; } } int main() { int t;cin>>t; for(int i=0;i<t;i++) { int n;cin>>n;ll count=0; for(int j=0;j<n;j++) { ll team; cin>>team; int index=team+1; while(!isprime(index)) {index++;} count+=index; } //string s=" Xukha"; printf("Case %d: %lld Xukhan",(i+1),count); } return 0; }
歐拉篩
#include <iostream> #include<stdio.h> using namespace std; #define ll long long const int MAXN=1100000+7; int m; ll a[MAXN],euler[MAXN]; void phi() { for(int i=1;i<=m;i++) a[i]=i; for(int i=2;i<=m;i+=2) a[i]>>=1; for(int i=3;i<=m;i++) { if(a[i]==i) { for(int j=i;j<=m;j+=i) a[j]=(a[j]/i)*(i-1); } } } int main() { m=1100000; phi(); int t;cin>>t; for(int i=0;i<t;i++) { ll n,count; cin>>n;count=0; for(int j=0;j<n;j++) { ll team; cin>>team; int index=team+1; while(a[index]<team) {index++;} count+=index; } printf("Case %d: %lld Xukhan",(i+1),count); } return 0; }
java 歐拉打表(可以自己改成素數)
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.StreamTokenizer; import java.util.Scanner; public class Main{ public static void main(String[] args) throws IOException { // TODO 自動生成的方法存根 StreamTokenizer in=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in))); PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); in.nextToken();int t=(int)in.nval; int a[]=new int[1100001]; for(int i=1;i<1100001;i++) { a[i]=i; } for(int i=2;i+2<1100001;i+=2) { a[i]/=2; } for(int i=3;i+2<1100001;i+=2) { if(a[i]==i) { for(int j=i;j+i<=1100001;j+=i) { a[j]=a[j]/i*(i-1); } } } for(int i=0;i<t;i++) { in.nextToken();int n=(int)in.nval;long count=0; for(int j=0;j<n;j++) { in.nextToken(); long team=(long)in.nval; int index=(int) (team+1); while(a[index]<team) {index++;} count+=index; //System.out.println(oula(team)); } System.out.println("Case "+(i+1)+": "+count+" Xukha"); } } private static boolean isprime(int index) { if(index<=2)return true; else { for(int i=2;i*i<index+1;i++) { if(index%i==0)return false; } return true; } } private static int oula(int team) { int i=0;int res=team;int team1=team; for(i=2;i<(int)Math.sqrt(team1)+1;i++) { if(team%i==0) { res=res/i*(i-1); while(team%i==0) {team/=i;}//保證I是素數 } } if(team>1)res=res/team*(team-1); return res; } }
