本文版權歸ljh2000和博客園共有,歡迎轉載,但須保留此聲明,並給出原文鏈接,謝謝合作。
本文作者:ljh2000
作者博客:http://www.cnblogs.com/ljh2000-jump/
轉載請注明出處,侵權必究,保留最終解釋權!
Description
Input
第一行一個整數n,接下來n行每行五個整數,分別表示a、b、c、d、k
Output
共n行,每行一個整數表示滿足要求的數對(x,y)的個數
Sample Input
2 5 1 5 1
1 5 1 5 2
Sample Output
3
HINT
100%的數據滿足:1≤n≤50000,1≤a≤b≤50000,1≤c≤d≤50000,1≤k≤50000
正解:線性篩+莫比烏斯函數+分塊
解題報告:
這道題是一道莫比烏斯函數的入門題,我從昨晚一直看題解和分析,直到今天才完全弄懂。系統性地寫一份解題報告吧。
ps:我在學習的時候參考了這些論文與博客,貼出來以供參考吧:
http://wenku.baidu.com/view/fbe263d384254b35eefd34eb.html
http://wenku.baidu.com/view/fbec9c63ba1aa8114431d9ac.html
http://www.cnblogs.com/zhsl/p/3269288.html
可以發現題目要求的是∑ ∑ [gcd(i,j)==k] (1<=i<=n && 1<=j<=m),這樣一個東西。(至於區間的話我們很快可以發現根據容斥原理[a,b]和[c,d]組合起來的答案就等於[1,b]組合[1,d] - [1,a-1]組合[1][d] - [1][b]組合[1][c-1] + [1][a-1] 組合 [1][c-1],問題從而轉化。)但是直接求的復雜度很高,還是多組詢問,肯定會TLE。
題目所求的可以變成
∑ ∑ [gcd(i,j)==1] (1<=i<=n/k && 1<=j<=m/k) ,相當於是同時除以k;
考慮再把式子變形:
∑ ∑ ∑ μ(d) (1<=i<=n && 1<=j<=m && d|gcd(i,j));
根據莫比烏斯函數的性質,當n=1時,∑μ(i)=1(1<=i<=n);否則∑μ(i)=0(1<=i<=n),所以實際上只有在gcd(i,j)==1時上式才有貢獻。可是復雜度還是沒有改變啊。我看了一篇解題報告,里面對於這個問題講的很詳細,大概是說根據容斥原理,可以把1<=i<=n && 1<=j<=m且滿足gcd(i,j)==k的方案數轉化成1<=i<=n && 1<=j<=m且滿足gcd(i,j)>=k的方案數。二者的聯系就在於莫比烏斯函數。論文里面畫了一個圖,一目了然地展示了二者的聯系。
可以根據莫比烏斯函數的性質很快反應過來二者的聯系。
所以得到新式:
ans=∑μ(i)*[n/i]*[m/i] (1<=i<=min(n,m),[n/i]表示n/i下取整)
容易發現這個式子可以使我的復雜度降低到O(n),但是詢問次數也是5w級別,還是會TLE。考慮繼續優化上式,考慮到[n/i]、[m/i]都會有大量的完全相等的部分,我們可以把[n/i]、[m/i]都相等的部分放在一起算,也就是一個分塊的思想。容易發值為[n/i]的起點是i,終點是n/([n/i]),而取值不會超過根號n種,也就是說我只要算根號次。但是因為是兩個都要相等,在數軸上畫線段可以發現,這樣的話總取值不會超過2×根號n種(論文里面也有圖說明)。所以復雜度降低到可以接受的范圍內。
值得一提的是,由於我們是分塊計算的,所以我們必須要預處理出莫比烏斯函數和他的前綴和。預處理的方式是根據莫比烏斯函數的性質,進行一次線性篩。在篩出質數的同時隨便求一下莫比烏斯函數,比較巧妙,然后直接構前綴和就可以了。
1 //It is made by ljh2000 2 #include <iostream> 3 #include <cstdlib> 4 #include <cstring> 5 #include <cstdio> 6 #include <cmath> 7 #include <algorithm> 8 #include <ctime> 9 #include <vector> 10 #include <queue> 11 #include <map> 12 #include <set> 13 using namespace std; 14 typedef long long LL; 15 const int inf = (1<<30); 16 const int MAXN = 50011; 17 const int MAXL = 50000; 18 LL a,b,c,d,k,ans; 19 int prime[MAXN],cnt; 20 bool ok[MAXN]; 21 int mobius[MAXN],sum[MAXN];//莫比烏斯函數及其前綴和 22 23 inline int getint() 24 { 25 int w=0,q=0; char c=getchar(); 26 while((c<'0' || c>'9') && c!='-') c=getchar(); if(c=='-') q=1,c=getchar(); 27 while (c>='0' && c<='9') w=w*10+c-'0', c=getchar(); return q ? -w : w; 28 } 29 30 inline void init(){//遞推得到莫比烏斯函數 31 //1的莫比烏斯函數是1;質數的莫比烏斯函數為1;含有相同質因子的數莫比烏斯函數為0; 32 //不含有相同質因子的數的莫比烏斯函數函數為(-1)^k,k為質因子個數 33 mobius[1]=1; 34 for(int i=2;i<=MAXL;i++) { 35 if(!ok[i]) { 36 mobius[i]=-1; 37 prime[++cnt]=i; 38 } 39 for(int j=1;j<=cnt && i*prime[j]<=MAXL;j++) { 40 ok[i*prime[j]]=1;//標記合數 41 if(i%prime[j]) mobius[i*prime[j]]=-mobius[i];//互質的兩個數乘起來得到一個不含有相同質因子的數,質因子個數奇偶性改變,莫比烏斯函數變號 42 else { mobius[i*prime[j]]=0; break; }//留到后面再篩,此處已經可以break 43 } 44 } 45 for(int i=1;i<=MAXL;i++) sum[i]=sum[i-1]+mobius[i];//求莫比烏斯函數的前綴和 46 } 47 48 inline LL solve(LL n,LL m){//計算a在[1,n]且b在[1,m]中的gcd(a,b)==1的數目 49 n/=k; m/=k; 50 if(n>m) swap(n,m); if(n==0) return 0; 51 LL i,next1,next2,next;//把相等的部分直接分塊一起計算 52 LL tot=0; 53 for(i=1;i<=n;i=next) { 54 next1=n/(n/i); next2=m/(m/i); 55 next=min(next1,next2); 56 tot+=(n/i)*(m/i)*(sum[next]-sum[i-1]); 57 next++; 58 } 59 return tot; 60 } 61 62 inline void work(){ 63 int T=getint(); init(); 64 while(T--) { 65 a=getint(); b=getint(); c=getint(); d=getint(); k=getint(); 66 ans=solve(b,d)-solve(a-1,d)-solve(b,c-1)+solve(a-1,c-1);//容斥原理 67 printf("%lld\n",ans); 68 } 69 } 70 71 int main() 72 { 73 work(); 74 return 0; 75 }