1. 程式人生 > >20170529_3 數論_gcd 題解

20170529_3 數論_gcd 題解

個數 操作 build 進行 比較 都是 題目中的 了吧 problem

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 我們用(91,49)表示91和49的最大公約數.按劉徽所說,分別列出分子,分母,”以少減多,更相減損,求其等也,以等數約之,等數約之,即除也,其所以相減者皆等數之重疊,故以等數約之.”譯文如下:約分的法則是:若分子、分母均為偶數時,可先被2除,否則,將分子與分母之數列在它處,然後以小數減大數,輾轉相減,求它們的最大公約數,用最大公約數去約簡分子與分母。其與古希臘歐幾裏德所著的《幾何原本》中卷七第一個命題所論的相同。列式如下: 91≡42(mod49) 49≡7(mod42) 7│42 再來看一個栗子: (24,15)->(9,15)->(9,6)->(3,6)->(3,3) 說明了: 每次所得兩數與前兩數有相同的等數,兩數之值逐步減少,因而到有限步後必然獲得相同的兩數,也即所求的等數,其理由不證自明. 證明更相減損法成立! 用現代語言來說就是 (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])

對於題目中所說每個數都加增加了多少其實不需要使用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 題解