大概就是寫一些數論水題的題解?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; }
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; }