莫比烏斯函式ACM
莫比烏斯入門請耐心往下看:
OK.現在可以開始刷題了。
從區間[1, b]和[1,d]中分別選一個x, y,使得gcd(x, y) == k, 求滿足條件的xy的對數(不區分xy的順序)
分析:轉換成求[1,b/k],[1,d/k]中gcd(x,y)==1的(x,y)對數.需要知道下面這個定理
然後把這個定理換成程式碼就好
#include<vector> #include<cmath> #include<algorithm> #include<string> #include<iostream> #include<cstdio> #include<cstring> #include<map> #define sf(n) scanf("%d", &n) #define sff(a,b) scanf("%d %d", &a, &b) #define sfff(a,b,c) scanf("%d %d %d", &a, &b, &c) #define MT(x,i) memset(x,i,sizeof(x)) typedef long long LL; using namespace std; const int MAXN = 100000; /*//線性篩法求莫比烏斯函式 bool check[MAXN+10]; int prime[MAXN+10]; int mu[MAXN+10]; void Moblus() { memset(check,false,sizeof(check)); mu[1] = 1; int tot = 0; for(int i = 2; i <= MAXN; i++) { if( !check[i] ) { prime[tot++] = i; mu[i] = -1; } for(int j = 0; j < tot; j++) { if(i * prime[j] > MAXN) break; check[i * prime[j]] = true; if( i % prime[j] == 0) { mu[i * prime[j]] = 0; break; } else { mu[i * prime[j]] = -mu[i]; } } } } */ int mu[MAXN+10]={0}; void getMu(){//n*logn for(int i=1; i<=MAXN; i++) { int target = i==1?1:0; int delta = target - mu[i]; mu[i]=delta; for(int j=i*2; j<=MAXN; j+=i) mu[j]+=delta; } } int main() { getMu(); //Moblus(); //for(int i=1; i<=50; i++) printf("%d %d\n",i,mu[i]); int a,b,c,d,k,T,cas=1; for(sf(T);T--;){ scanf("%d%d%d%d%d",&a,&b,&c,&d,&k); if(k==0){printf("Case %d: 0\n",cas++);continue; } b/=k,d/=k; if(b>d) swap(b,d); LL ans=0,rep=0;//答案,重複個數 for(int i=1;i<=b;i++){ ans+=(LL)mu[i]*(b/i)*(d/i); rep+=(LL)mu[i]*(b/i)*(b/i); } ans=ans-rep/2; printf("Case %d: %I64d\n",cas++,ans); } return 0; }
求莫比烏斯函式模板如下:兩種求法,第一種更快,但程式碼量大。第二種程式碼少,但耗時久。
const int MAXN = 100000; //線性篩法求莫比烏斯函式 bool check[MAXN+10]; int prime[MAXN+10]; int mu[MAXN+10]; void Moblus() { memset(check,false,sizeof(check)); mu[1] = 1; int tot = 0; for(int i = 2; i <= MAXN; i++) { if( !check[i] ){ prime[tot++] = i; mu[i] = -1; } for(int j = 0; j < tot; j++) { if(i * prime[j] > MAXN) break; check[i * prime[j]] = true; if( i % prime[j] == 0){ mu[i * prime[j]] = 0; break; }else{ mu[i * prime[j]] = -mu[i]; } } } } /*int mu[MAXN+10]={0}; void getMu(){ //n*logn 遞推篩法 for(int i=1; i<=MAXN; i++) { int target = i==1?1:0; int delta = target - mu[i]; mu[i]=delta; for(int j=i*2; j<=MAXN; j+=i) mu[j]+=delta; } }*/
1 2 3 4 5 6 7 8 9 10 11 12 13
0 -1 -1 0 -1 1 -1 0 0 1 -1 0 -1
http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=37166
對於給出的 n 個詢問, 每次求有多少個數對(x,y), 滿足 a≤x≤b, c≤y≤d, 且 gcd(x,y) = k,
gcd(x,y)函式為 x 和 y 的最大公約數。
1≤n≤50000,1≤a≤b≤50000,1≤c≤d≤50000,1≤k≤50000
資料量大,需要分段優化
#include<algorithm> #include<cstdio> #include<cstring> #define sf(n) scanf("%d", &n) typedef long long LL; using namespace std; const int MAXN = 50000+5; bool check[MAXN+10]; int prime[MAXN+10],mu[MAXN+10],sum[MAXN+10]; void Moblus() { memset(check,false,sizeof(check)); mu[1] = 1; int tot = 0; for(int i = 2; i <= MAXN; i++){ if( !check[i] ){ prime[tot++] = i; mu[i] = -1; } for(int j = 0; j < tot; j++){ if(i * prime[j] > MAXN) break; check[i * prime[j]] = true; if( i % prime[j] == 0){ mu[i * prime[j]] = 0; break; } else mu[i * prime[j]] = -mu[i]; } } } LL calc(int n,int m)//計算[1,n]和[1,m]中gcd==1的數對。(3,2),(2,3)算兩個 { LL ret=0; if(n>m) swap(n,m); for(int i=1,next=0;i<=n;i=next+1){ int d1=n/i,d2=m/i; int next1=n/d1,next2=m/d2; next=min(next1,next2); ret+=(LL)(sum[next]-sum[i-1])*d1*d2; } return ret; } int main() { Moblus(); for(int i=1;i<=MAXN;i++) sum[i]=sum[i-1]+mu[i]; int a,b,c,d,k,T; for(sf(T);T--;){ scanf("%d%d%d%d%d",&a,&b,&c,&d,&k); LL ans=calc(b/k,d/k)-calc((a-1)/k,d/k)-calc((c-1)/k,b/k)+calc((a-1)/k,(c-1)/k); printf("%lld\n",ans); } return 0; }
求一個n*n*n的晶體,有多少點可以在(0,0,0)處可以直接看到。
也就是不是有點在它們之間,即它們gcd(x,y,z)==1. 加上特例:3個軸+3個平面
/*
這題是求(0,0,0)~(N,N,N)中gcd(a,b,c)=1的點的個數
顯然就是莫比烏斯反演的板題了...很容易就能得出F(1) =sigma(miu(d)*(n/d)*(n/d)*(n/d))
但這求出來的是(1,1,1)~(N,N,N)中的點的個數...還要加上含0的三個平面內的點...
因為對稱...所以直接加上二維上的整點數*3
最後再加上(0,0,1),(1,0,0),(0,1,0)三個點...就OK了...
*/
#include<vector>
#include<cmath>
#include<algorithm>
#include<string>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#define sf(n) scanf("%d", &n)
#define sff(a,b) scanf("%d %d", &a, &b)
#define sfff(a,b,c) scanf("%d %d %d", &a, &b, &c)
#define MT(x,i) memset(x,i,sizeof(x))
typedef long long LL;
using namespace std;
const int MAXN = 1000000;
//線性篩法求莫比烏斯函式
bool check[MAXN+10];
int prime[MAXN+10];
int mu[MAXN+10];
int sum[MAXN+10];
void Moblus()
{
memset(check,false,sizeof(check));
mu[1] = 1;
int tot = 0;
for(int i = 2; i <= MAXN; i++)
{
if( !check[i] )
{
prime[tot++] = i;
mu[i] = -1;
}
for(int j = 0; j < tot; j++)
{
if(i * prime[j] > MAXN) break;
check[i * prime[j]] = true;
if( i % prime[j] == 0)
{
mu[i * prime[j]] = 0;
break;
}
else
{
mu[i * prime[j]] = -mu[i];
}
}
}
}
int main()
{
Moblus();
int T,n;
for(sf(T);T--;){
scanf("%d",&n);
LL ans=3;//x,y,z軸上面的三點.(1,0,0) (0,1,0) (0,0,1);
for(int i=1;i<=n;i++)
ans+=(LL)mu[i]*(n/i)*(n/i)*(n/i+3);
//ans+= (LL)mu[i]*(n/i)*(n/i)*(n/i)+(LL)mu[i]*(n/i)*(n/i)*3; //三維+二維*3
printf("%lld\n",ans);
}
return 0;
}
/*
這題是求(0,0,0)~(N,N,N)中gcd(a,b,c)=1的點的個數
顯然就是莫比烏斯反演的板題了...很容易就能得出F(1) = sigma(miu(d)*(n/d)*(n/d)*(n/d))
但這求出來的是(1,1,1)~(N,N,N)中的點的個數...還要加上含0的三個平面內的點...
因為對稱...所以直接加上二維上的整點數*3
最後再加上(0,0,1),(1,0,0),(0,1,0)三個點...就OK了...
*/