關於一些信息學數論問題例題的討論

  大概就是寫一些數論水題的題解?html

  目錄算法

  不按期更新。ide


 

一.[AHOI2005]約數研究 洛谷oj P1403學習

  可能須要事先學習的算法:優化

    埃氏篩法(素數篩)spa

  題意很容易理解。很明顯這是一道真正的水題,適合初學者理解篩法的思想。翻譯

  30分暴力作法調試

    對於一個數$i(i∈[1,n])$ ,枚舉全部$[1,i)$之間的正整數$j$,判斷$j$是不是$i$的約數,若是是,計數器$result$就加上1。code

    複雜度是$O(n^2)$,不是頗有討論價值,寫了一下代碼。htm

#include <cstdio>
using namespace std;

int n;
int result;

int main(){
    scanf("%d",&n);
    for (int i=1;i<=n;i++)    //枚舉 1~n 全部數 
        for (int j=1;j<=i;j++)    //一個個判斷是不是i的約數,若是是,則計數加1 
            if (i%j==0)    result+=1;
    printf("%d",result);
    return 0;
}
View Code

 

  100分算法(暴力篩法):

    或許能夠當作暴力作法的優化,可是若是學過篩法,那就直接是篩法的思想了。

    我試着用優化暴力的思路解釋一下個人算法。上面的作法是先抓一個數$i(i∈[1,n])$,而後再一個個找它們的約數的。咱們能夠換個思路,也抓一個數$i(i∈[1,n])$,而後一個個找它的倍數(倍數小於$n$),找到一個倍數,計數器$result$就加上1。

    熟悉篩法的同窗應該能一眼AC吧(畢竟是普及組水題)。

    複雜度應該是$O(n\sqrt{n})$。

 

#include <cstdio>
using namespace std;

int n;
int result;

int main(){
    scanf("%d",&n);
    result+=n;    //1能夠是全部數的約數 
    for (int i=2;i<=n;i++)
        for (int j=1;i*j<=n;j++)    //枚舉倍數 i*j 
            ++result;
    printf("%d",result);
    return 0;
}

    基於這種想法其實還能夠優化。咱們很容易發現,第二重循環實際上是沒必要要的,由於對於一個數$i$,$[1,n]$裏它的倍數必定有且僅有$\frac{n}{i}$個(向下取整)。那麼咱們就能夠扔掉第二重循環了。

 

#include <cstdio>
using namespace std;

int n;
int result;

int main(){
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
        result+=(n/i);
    printf("%d",result);
    return 0;
}

 

    是否是已經挺不錯的了?可是洛谷上有神犇給出了下一種複雜度更加優秀的算法。

  100分算法(很是優秀):Kelin的題解

    大概意思是說,$f(i)=\frac{n}{i}$,可是由於除法要向下取整,因此有一些數能夠當成同一個數來跳過。

    打個比方,對於$n=60$時,無論$i=13$或$i=14$或$i=15$,$\frac{n}{i}$的結果都是同樣的,由於$int$整型要向下取整。因此能夠把它們放在一塊兒算,差很少就是這種思想。

    時間複雜度$O(2\sqrt{n})$。我測了一下,可能由於數據比較水,我寫的算法$36ms$跑完,這種算法$26ms$跑完,仍是十分優秀的。

    代碼在上面連接裏有,我就不寫了。

 

二.最大公約數和最小公倍數問題  洛谷oj P1029

  必須預先學習的算法:

    歐幾里得算法(GCD)(展轉相除法)

  這是一道數論入門好題。在作以前要熟悉$gcd$(即最大公約數)。

  這題我以爲不太可能有靠譜的部分分寫法(畢竟比較水),我就直接講正解了。

  你們都知道怎麼求最大公約數$gcd(P,Q)$,也許有人會問是否是也有專門求最小公倍數$lcm(P,Q)$的算法?不須要。最小公倍數$lcm(P,Q)$能夠經過最大公約數$gcd(P,Q)$獲得。

  引理:兩個正整數$P$,$Q$的最小公倍數爲$P*Q/gcd(P,Q)$。

  證實:

    記$P=gcd(P,Q)*p_{1}$

      $Q=gcd(P,Q)*p_{2}$,且$gcd(p_{1},p_{2})=1$  //即$p_{1}$和$p_{2}$互質

      $lcm(P,Q)=gcd(P,Q)*p_{1}*p_{2}$

          $=gcd(P,Q)*p_{1}*gcd(P,Q)*p_{2}/gcd(P,Q)$

          $=P*Q/gcd(P,Q)$

    得證。

   確定有人不喜歡讀證實,那我舉個例子好了。假設存在兩個數,$P=2160$,$Q=4032$。根據惟一分解定理,可得:

    $P=2160=2^4*3^3*5$

    $Q=4032=2^6*3^2*7$

  能夠看出來,這時候$gcd(P,Q)=2^4*3^2=144$,那麼,$P$和$Q$能夠這樣改寫:

    $P=gcd(P,Q)*3^1*5$

    $Q=gcd(P,Q)*2^2*7$

  很明顯,$3^1*5$或$2^2*7$互質,由於若是它們不互質,它們的最大公約數徹底能夠變成$gcd(P,Q)$的一個因子。

  又由於$lcm(P,Q)=2^6*3^3*5*7$,$lcm(P,Q)$具備$P$和$Q$的全部因子,則:

    $lcm(P,Q)=gcd(P,Q)*2^2*3^1*5*7$

         $=(gcd(P,Q)*3^1*5)*(gcd(P,Q)*2^2*7)/gcd(P,Q)$

         $=P*Q/gcd(P,Q)$

  就能夠根據$lcm(P,Q)=P*Q/gcd(P,Q)$求解了。理解力好的同窗應該能夠直接理解這個結論。

  在知道$lcm(P,Q)=P*Q/gcd(P,Q)$後,再來看這道題。在這道題裏,$x$是最大公約數$gcd(P,Q)$,而$y$是最小公倍數$lcm(P,Q)$。

  咱們不妨設$P=x*p_{1}$,$Q=x*p_{2}$($p_{1}$和$p_{2}$互質)。

  因此咱們能夠寫出下面的推導

    $y=P*Q/x$

      $=(x*p_{1})*(x*p_{2})/x$

      $=p_{1}*p_{2}*x$

    則$\frac{y}{x}=p_{1}*p_{2}$

  是否是發現了什麼?題目要求輸出的答案是$P$和$Q$,而$P=x*p_{1}$,$Q=x*p_{2}$,且$x$是已知的。要想知道$P$、$Q$的全部可能性,只須要枚舉出$p_{1}$和$p_{2}$的全部可能性就行了。

  怎麼枚舉出$p_{1}$和$p_{2}$?咱們已經知道$\frac{y}{x}=p_{1}*p_{2}$了,$for$一遍就行了。

  代碼:

 

#include <cstdio>
using namespace std;
const int maxn=100000;

int x,y;
int result;

inline int gcd(int a,int b){
    return b?gcd(b,a%b):a;
}

int main(){
    scanf("%d%d",&x,&y);
    if (y%x)    printf("0");    //若是y不能整除x,不存在解
    else{
        int n=y/x;
        for (int i=1;i<=n;i++)
            if (n%i==0){
                if (gcd(i,n/i)==1)
                    result+=1;
            }
        printf("%d",result);
    }
    return 0;
}

 

 

 三.又是畢業季I  洛谷oj P1372

  須要預先學習的算法:

感受不須要預先學習算法?可能須要一點對質數的理解。

  一道你們都很高興作的水題,能夠加強對質數的理解。

  題意大概就是要在$1~n$中找到$k$個最大公倍數最大的數。

  很容易想到,假如$k$個數存在最大公倍數$gcd$,則$k=gcd*m$,$m$必定是正整數。簡單地說,就是這些被選中的$k$個數要麼就是$gcd$,要麼就是$gcd$的倍數。

  由於$k$和$n$已經肯定,如何讓$gcd$最大?咱們很容易想到,$gcd=\frac{n}{k}$,注意,這裏的除法須要向下取整

  代碼就更簡單了,複雜度$O(1)$不須要分析了。

#include <cstdio>
using namespace std;

int n,k;

int main(){
    scanf("%d%d",&n,&k);
    printf("%d",n/k);
    return 0;
} 

 

四.倒水  洛谷oj P1582

須要一點對二進制的理解

  玩過《2048》這款遊戲嗎?我以爲和這道題很像。

  很明顯,全部瓶子的狀態能夠壓成一個二進制數。

  好比這個二進制數:

    $1100101$

  表示的是,通過處理後,有$64$升水、$32$升水、$4$升水、$1$升水的瓶子各一個。

  爲何能夠這麼壓?假設有$X$升水的瓶子$Y$個,很顯然,X必定是$2$的幾回方(咱們先表示成$X=2^i$),而若是$Y$大等於$2$,則$Y$必定能夠倒進更多水的瓶子裏(兩瓶$X$就能夠變成一瓶$2X$)。由於倒水次數不限制,因此貪心的想法是,初始狀態每一個瓶子裏的水越多越好,這樣瓶子就少了。

  因此很明顯,若是某升水的瓶子數量大於等於$2$,必定能夠把多餘的水往上倒。初始狀態處理到最後,就是二進制數了。而二進制數逢二進一的原則保證了不管多少個1升的瓶子均可以自動處理成最少的瓶數。

  因此,當有$a$個1升的瓶子時,處理完後最少的瓶數就是$a$轉換成二進制後的$1$的個數。好比這個二進制數$1100101$最少的瓶數就是$4$。

  想明白了這一點,這題就很好寫了。接下去怎麼處理就很容易了,題意翻譯過來就是求大等於$N$的最小數,使這個數含有的$1$的個數不大於$K$。而後的結果輸出這個最小數減去$N$。

  90分暴力算法(最後一個點過不了):

#include <cstdio>
using namespace std;

int n,k;
long long result;

inline int count(long long x){    //數瓶子的個數
    int cnt=0;
    while (x){
        if (x&1)    cnt+=1;
        x>>=1;
    } 
    return cnt;
}

int main(){
    scanf("%d%d",&n,&k);
    for (result=0;count(result+n)>k;result++);
    printf("%lld",result);
    return 0;    
} 

 

  100分算法

    很早之前A的,忘記是怎麼優化的了,具體能夠直接看代碼。若是有時間我再回來補充詳細解釋。

    記得開longlong,不開仍是90分.

#include <cstdio>
using namespace std;

long long n,k;
long long cnt;
long long result;

inline long long count(long long x){    //數瓶子的個數
    long long cnt=0;
    while (x){
        if (x&1)    cnt+=1;
        x>>=1;
    } 
    return cnt;
}

int main(){
    scanf("%d%d",&n,&k);
    cnt=count(n),result=n;
    for (int i=0;cnt>k;i++)
        if ((result>>i)&1){
            result-=(1<<i);
            i+=1;
            while (true)
                if ((result>>i)&1){
                    result+=(1<<i);
                    break;
                }
                else i++;
            cnt=count(result);
        }
    printf("%lld",result-n);
    return 0;    
} 

 

五.階乘問題  洛谷oj P1134

  調了快一個小時才調出來……給跪了。

  題意很清楚了。

  28分騙分算法(皮一下)

    打表可知 能夠詳細證實,階乘的尾數只多是$2$、$4$、$6$、$8$。因此隨便抓個數騙分/滑稽

#include <cstdio>
using namespace std;

int main(){
    printf("2");
    return 0;
}

  100分算法

    咱們很快能夠想到,每次都取最後一個有效數字來乘下一個數,這樣複雜度$O(N)$是能夠過的。

    可是,在調試的過程當中,會發現取最後一個有效數字是不行的,爲何我就不解釋了。差很少要模10的7次方或8次方纔能過。

    和上一道題目同樣,記得開longlong。

#include <cstdio>
using namespace std;

int n;
long long result=1;

int main(){
    scanf("%d",&n);
    for (int i=1;i<=n;i++){
        result*=i;
        while (result%10==0)    result/=10; 
        result%=100000000;
    }
    printf("%d",result%10);
    return 0;
}
相關文章
相關標籤/搜索