首页 > 代码库 > 20170529_3 数论_gcd 题解
20170529_3 数论_gcd 题解
1.LCM Range最小公倍数
其实就是求 l 到 r 这么多自然数的最小公倍数。
需要注意LCM的求法,
理论:a与b的最小公倍数=a*b/gcd(a,b)。
这里,lcm=ans*i*gcd(ans,i)
在后面的学习中由于ans*i可能很大,容易爆
所以可以写作:lcm=ans*gcd(ans,i)*i (交换律)
最终的答案由于就是当前答案和后一个数的lcm
代码如下:
var l,r,i,ans:longint; function gcd(x,y:qword):qword; begin if y=0 then gcd:=x else gcd:=gcd(y,x mod y); end; begin assign(input,‘lcm.in‘); assign(output,‘lcm.out‘); reset(input); rewrite(output); readln(l,r); ans:=1; for i:=l to r do ans:=ans div gcd(ans,i)*i ;//迭代 writeln(ans); close(input); close(output); end.
2.最大公约数(gcd.pas/c/cpp)
下面要解析的是一道 NOI 2012 chess 的原题!orz
请首先看:对于 100%:N, Q <= 100000,所有数的绝对值始终小于等于 10^16
这是数据范围,吓蒙了!果然是NOI难度啊(其实这种难度好像算NOI历史最低了吧?)
模拟就不用说了吧,太简单!于是就来对于100%数据的题解。
这里用到gcd的求法2:
更相减损法
“关于约分问题,实质是如何求分子,分母最大公约数的问题.<九章算术>中介绍了这个方法,叫做”更相减损术”,即“可半者半之,不可半者,副置分母、子之数,以少减多,更相减损,求其等也。以等数约之。”
第一步:任意给定两个正整数;判断它们是否都是偶数。若是,则用2约简;若不是则执行第二步。
第二步:以较大的数减较小的数,接着把所得的差与较小的数比较,并以大数减小数。继续这个操作,直到所得的减数和差相等为止。
则第一步中约掉的若干个2与第二步中等数的乘积就是所求的最大公约数。
其中所说的“等数”,就是最大公约数。求“等数”的办法是“更相减损”法。所以更相减损法也叫等值算法。
gcd(a[1], a[2]) = gcd(a[1], a[2] – a[1])
gcd(a[2], a[3]) = gcd(a[2], a[3] – a[2])
gcd(a[1], a[2], a[3]) = gcd(a[1], a[2]–a[1], a[3]–a[2])
………
gcd(a[1], … , a[n]) = gcd(a[1], a[2]–a[1],………,a[n]–a[n - 1])
对于题目中所说每个数都加增加了多少其实不需要使用O(n)的算法
这里可以看到:上面的式子中除了a[1]其他都是不变的!
即 d=a[2]–a[1],………,a[n]–a[n - 1](d为常数)
于是原来O(n)的算法变成了O(1)累加a[1]即可!
在读入a[2..n]数据时就处理gcd,得到常数d,
对于1到Q就形成O(N) – O(1) 在线算法。
总结一下本题思路:怎么求gcd?
(a, b) = (b, a mod b)--->(a, b) = (a, a - b)
gcd(a[1], a[2]) = gcd(a[1], a[2] – a[1])
gcd(a[2], a[3]) = gcd(a[2], a[3] – a[2])
gcd(a[1], a[2], a[3]) = gcd(a[1], a[2]–a[1], a[3]–a[2])
………
gcd(a[1], … , a[n]) = gcd(a[1], a[2]–a[1],………,a[n]–a[n - 1])
上面的式子中除了a[1]其他都是不变的!因此可事先预处理出来
即读入a[2..n]数据时就处理gcd,得到d,但需要考虑a[i]-a[i-1]可能为负数(简单的将其为abs(a[i]-a[i-1]))。
每次修改a[1],求gcd(a[1],d)即是答案。
O(N) – O(1) 在线算法
代码如下:
var n,q,i:longint; a:array[0..100000]of int64; d,t:int64; function gcd(a,b:int64):int64; begin if a<0 then a:=-a; if b<0 then b:=-b; if b=0 then exit(a) else exit(gcd(b,a mod b)); end; begin assign(input,‘gcd.in‘); assign(output,‘gcd.out‘); reset(input); rewrite(output); readln(n,q); d:=0; for i:=1 to n do begin read(a[i]); if i<>1 then d:=gcd(a[i]-a[i-1],d); end; for i:=1 to q do begin read(t); a[1]:=a[1]+t; writeln(gcd(a[1],d)); end; close(input); close(output); end.
3.约数统计AHOI2005
安徽省队选拔的水题!但是还是orz
N=4时:
1:1
2:1、2
3:1、3
4:1、2、4
我们可以发现 总和中1出现了n次,2出现了n/2次,3出现了n/3次……答案就是n+n/2+n/3+n/4(这里的/其实是div)
于是推广一下:
考虑[1,n]中每个数的约数当中有i的数就是n div i个。因此i对答案的贡献就是n div i。
∴ans=(n+n/2+n/3+……+n/n )mod (10^9+7)(这里的/其实是div)
程序如下:
var n,i,sum,p:longint; begin assign(input,‘1.in‘); assign(output,‘1.out‘); reset(input); rewrite(output); p:=1000000007; readln(n); for i:=1 to n do sum:=(sum+n div i)mod p; writeln(sum mod p); close(input); close(output); end.
4.最轻的天平 (mobile.c/cpp/pas)L1961
https://www.luogu.org/problem/show?pid=1961
有没有发现其实这个天平有点像完全二叉树?我也觉得像。
解决这道题需要3个分析知识:
●题目中的隐含条件为挂的物品必须为整数,即每个天平悬挂的物品重量必须为整数。(没什么好解释)
●题目的约束条件即为天平必须平衡,即重量与长度的乘积必须相等。(简单机械中的杠杆原理)
●左右没有天平(叶)的天平左右的单位重量都为1,该杠杆的最轻重量即为(p+q) div gcd(p,q) (转化一下就是 p div gcd(p,q) + q div gcd(p,q))
总体的算法思想是:
若天平i左边的最轻重量为x,右边为y,则左边重量为ax,右边为by
∴ax*p=by*q,a:b=qy:xp,我们使a:b最简即可(求gcd),ax+by即为天平的最轻重量。
(a,b∈N*) 是指a,b均为自然数
但是我们都不知道根节点是哪个怎么求呢?
这里用到类似并查集的算法:
for i:=1 to n do begin readln(p[i],q[i],r[i],b[i]); fa[r[i]]:=true; fa[b[i]]:=true; end; for i:=1 to n do if fa[i]=false then root:=i;
这样我们就找到了根root。
随后就是递归建树(假假的树):
procedure build(k:longint; var ans:int64); var t,d1,d2:int64; begin if r[k]<>0 then build(r[k],d1) else d1:=1;//如果不是叶结点就递归深入,否则假定根节点是1开始累加退回 if b[k]<>0 then build(b[k],d2) else d2:=1;//同上 t:=(p[k]*d1)*(q[k]*d2) div gcd(p[k]*d1,q[k]*d2);//上面解释里a:b最简的值,即该结点(天平)的最小重量 ans:=t div p[k]+ t div q[k];//预备知识3:该杠杆的最轻重量即为(p+q) div gcd(p,q) (转化一下就是 p div gcd(p,q) + q div gcd(p,q)) end;
于是就做好了!
代码如下:
var i,root,n:longint; sum:int64; fa:array[0..1000]of boolean; p,q,r,b:array[1..1000]of longint; function gcd(a,b:longint):longint; begin if b=0 then gcd:=a else gcd:=gcd(b,a mod b); end; procedure build(k:longint; var ans:int64); var t,d1,d2:int64; begin if r[k]<>0 then build(r[k],d1) else d1:=1; if b[k]<>0 then build(b[k],d2) else d2:=1; t:=(p[k]*d1)*(q[k]*d2) div gcd(p[k]*d1,q[k]*d2); ans:=t div p[k]+ t div q[k]; end; begin assign(input,‘mobile.in‘); reset(input); assign(output,‘mobile.out‘); rewrite(output); readln(n); for i:=1 to n do begin readln(p[i],q[i],r[i],b[i]); fa[r[i]]:=true; fa[b[i]]:=true; end; for i:=1 to n do if fa[i]=false then root:=i; build(root,sum); writeln(sum); close(input);close(output); end.
20170529_3 数论_gcd 题解