線性篩法最基礎的功能就是求[1,n]中的素數,以此爲基礎,可以對他進行一些變形。變形後的線性
篩法可以實現許多其他的功能。(下文中的tot均指一定區間內的質數個數)
先看一道簡單的問題:求[1,n]中的m個數的最大質因子的序數(hdoj2136)這個問題可以利用線性篩法打一個質數表,然後二分答案,複雜度爲O(n+mlog(tot))。
然而,如果m很大,算法必然會超時。我們可以對線性篩法進行一些變形,每次篩掉一個合數時,
可以通過他的兩個約數推出他的最大質因子。這樣可以得到一個O(maxn+m)的算法。
Code:
program hdoj_2136;
type
int=int64;
var
i,j:longint;
k,m,n:int;
a,ans:array[1..1001000]of int;
prime:array[1..100000]of int;
tot:int;
function max(x,y:int):int;
begin
if x>y then max:=x
else max:=y;
end;
begin
assign(input,'input.txt');reset(input);
assign(output,'bb.out');rewrite(output);
ans[1]:=0;
n:=1000100;tot:=0;
for i:=2 to n do begin
if a[i]=0 then begin
inc(tot);prime[tot]:=i;
ans[i]:=tot;
end;
for j:=1 to tot do begin
if(prime[j]*i<=n)then begin
k:=prime[j]*i;
a[k]:=1;
ans[k]:=max(ans[i],j);
end else break;
if(i mod prime[j]=0)then break;
end;
end;
while not eof do begin
read(n);
if n=0 then break;
writeln(ans[n]);
end;
close(output);
end.
同樣的,每次篩掉一個合數時,也可以記下他的最小質因子,這樣就可以在log(n)以內的時間內
給出一個數的質因數分解,也就可以同時求出phi(n)的值。
再看一道問題:求出n!的質因數分解,輸出爲若干行,每行第一個數爲一個質數,第二個數爲它的次
數,時間限制爲兩秒。(n<=10^7)
這道題很容易想到非線性的做法,即按以上的方法篩一遍,再一個一個的求質因數分解,最後加起
來。原題的數據範圍是3*10^6,用好一點的實現的話完全是可以過的。但是,這道題數據範圍更大,經測試
這種算法在我的電腦上需要2.5s以上才跑得出來,我們需要一個更優秀的算法。
篩法已經是線性的了,算法的瓶頸在於分解質因數。
分析一下算法一分解質因數的過程:對一個數k分解質因數時,每次找到他的最小質因數s,然後
k div s,繼續分解。我們發現,每次k都變成一個更小的合數,而這個合數本身在後面還要分解一次!!!
也就是說,對於k和2k,分解2k的時候把k也分解了一次,這是冗餘的!!!
由於k*(s*k)的質因數分解等於k^2的質因數分解加上s的質因數分解,因此我們可以構造一個線性
算法。開一個數組b,初值賦爲1。令指針i從後往前掃,每次掃到一個數k,若k是質數,跳過他。否則我們
可以知道k=s*(k div s),b[s]=b[k]+b[s];b[k div s]=b[k div s]+b[k]。這樣,我們就可以在線性時間內
求出n!的質因數分解了。
算法一的程序:
program al_1;
type
int=int64;
var
i,j:longint;
k,m,n:int;
a,b:array[1..10000000]of longint;
prime:array[1..1000000]of longint;
tot:longint;
begin
assign(output,'ans.txt');rewrite(output);
read(n);
tot:=0;
for i:=2 to n do begin
if a[i]=0 then begin
inc(tot);prime[tot]:=i;a[i]:=i;
end;
for j:=1 to tot do begin
k:=prime[j]*i;
if k<=n then a[k]:=prime[j]
else break;
if i mod prime[j]=0 then break;
end;
end;
for i:=2 to n do begin
if a[i]=i then continue;
k:=i;
while k<>1 do begin
inc(b[a[k]]);
k:=k div a[k];
end;
end;
for i:=1 to tot do writeln(prime[i],' ',b[prime[i]]+1);
close(output);
end.
算法二的程序:
program al_2;
type
int=int64;
var
i,j:longint;
k,m,n,tot:int;
a,b:array[1..10000000]of longint;
prime:array[1..1000000]of int;
begin
assign(output,'ans.txt');rewrite(output);
n:=10000000;
tot:=0;
for i:=2 to n do begin
if a[i]=0 then begin
inc(tot);prime[tot]:=i;
end;
for j:=1 to tot do begin
k:=prime[j]*i;
if k<=n then a[k]:=i
else break;
if i mod prime[j]=0 then break;
end;
b[i]:=1;
end;
for i:=n downto 2 do begin
if a[i]=0 then continue;
k:=b[i];
inc(b[a[i]],k);inc(b[i div a[i]],k);
end;
for i:=1 to tot do writeln(prime[i],' ',b[prime[i]]);
close(output);
end.
我用來測時間的程序:
program time;
uses dos;
var
h1,h2,m1,m2,s1,ss1,s2,ss2:word;
procedure get(filename:string);
begin
gettime(h1,m1,s1,ss1);
exec(filename,'');
gettime(h2,m2,s2,ss2);
write(filename,' ',(3600*(h2-h1)+60*(m2-m1)+s2-s1+(ss2-ss1)/100):0:2);
writeln('s');
end;
begin
get('a.exe');
get('b.exe');
end.
2012-11-15
線篩預處理約束個數:
for i:=2 to m do begin
if v[i]=0 then begin
inc(tot);Prime[tot]:=i;
mi[i]:=1;g[i]:=2;
end;
for j:=1 to tot do begin
k:=Prime[j]*i;
if k>m then break;
if i mod Prime[j]=0 then begin
mi[k]:=mi[i]+1;
g[k]:=(g[i]div mi[k])*(mi[k]+1);
v[k]:=1;
end else begin
g[k]:=g[i]*2;
mi[k]:=1;
v[k]:=1;
end;
end;
end;
poj 3421線性篩法+排列組合,O(n)預處理,O(1)回答
Code:
program poj3421;
type int=longint;
const maxn=1<<20;
var
i,j,k,m,n:int;
f,v,prime,a,b:array[1..maxn]of int;
ans:array[1..maxn]of int64;
procedure push(x,y:int);
begin
v[y]:=x;f[y]:=f[y div x]+1;
ans[y]:=ans[y div x]*f[y];
if y mod(x*x)=0 then begin
b[y]:=b[y div x]+1;
ans[y]:=ans[y]div b[y];
end else b[y]:=1;;
end;
begin
f[1]:=0;ans[1]:=1;
for i:=2 to maxn do begin
if v[i]=0 then begin
inc(m);Prime[m]:=i;
f[i]:=1;ans[i]:=1;
b[i]:=1;
end;
for j:=1 to m do begin
k:=prime[j];
if i*k>maxn then break;
push(k,i*k);
if i mod k=0 then break;
end;
end;
while not seekeof do begin
read(n);
writeln(f[n],' ',ans[n]);
end;
end.
BY QW
轉載請註明出處