大概就是写一些数论水题的题解?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; }