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 題解