基礎數論總結

  • 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的分解方式。主要是通過數的自身對從最小的質數開始整除除一直到不能整除,直到跳出限制條件。

  1. 你可以從2到n;逐個遍歷判斷,滿足條件的話就在數組中添加對應的count。當然,每被計算一次的時候,這個數就要被除一次。
  2. 上面方法對於大的數據顯然複雜度太高。其實你可以從2遍歷到根號n+1(i * i<n+1)停止,因為最大的理想乘積是i*i=n,其他的數據都是一個左,一個右面。最終分解的時候如果都分到這步了說明要麼後面不剩,要麼就是剩一個大質數。
  3. 上面雖然從數的量級減少了不少,但是會遍歷很多沒用的合數,比如遍歷過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個

  1. 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;  	}  }