1. 程式人生 > 其它 >Luogu P2158 儀仗隊【莫比烏斯反演】【線性篩】

Luogu P2158 儀仗隊【莫比烏斯反演】【線性篩】

前言

蒟蒻又來水部落格了!!!
昨天聽馮巨講解了莫比烏斯反演+線性篩法,馬上來寫一道模板題;
首先分析題意,我們用腦子推一下就知道了答案

\[\begin{aligned} Ans & =\sum_{x=1}^{n-1}\sum_{y=1}^{x-1}[gcd(i,j)=1]+1+\sum_{x=1}^{n-1}\sum_{y=x+1}^{n-1}[gcd(i,j)=1]\\ & =2*\sum_{x=1}^{n-1}\sum_{y=1}^{x-1}{[gcd(x,y)=1]}+1(n>1)\\ & =2*\sum_{x=1}^{n-1}\phi(x)+1(Euler)\\ & =\sum_{x=1}^{n-1}\sum_{y=1}^{n-1}\sum_{d|i,d|j}\mu(d)\\ & =\sum_{d=1}^n*\mu(d)\sum_{i=1}^{n/d}\sum_{j=1}^{m/d}1\\ & =\sum_{d=1}^{n}\mu(d)*{\frac{n}{d}}*{\frac{m}{d}}\\ \end{aligned}\]

中間是用尤拉函式,下面是用莫比烏斯反演。
兩種方法求答案,但是用莫比烏斯+分塊要快很多。

先放尤拉函式的程式碼:

點選檢視程式碼
#include<bits/stdc++.h>
using namespace std;

const int maxn=50000;
int vis[maxn];
int prime[maxn];
int phi[maxn];
int sum[maxn];

void get_phi(){//線性求尤拉函式字首和
	phi[1]=1;
	int cnt=0;
	for(int i=2;i<maxn;i++){
		if(!vis[i]){
			vis[i]=i;
			prime[cnt++]=i;//質數
			phi[i]=i-1;//尤拉函式
		}
		for(int j=0;j<cnt;j++){
			if(i*prime[j]>maxn) break;
			vis[i*prime[j]]=prime[j];
			if(i%prime[j]==0){//不是最小質因子
				phi[i*prime[j]]=phi[i]*prime[j];//積性函式
				break;
			}
			phi[i*prime[j]]=phi[i]*phi[prime[j]];
		}
	}
}
int main()
{
	get_phi();
	sum[1]=1;
	for(int i=2;i<=maxn;i++){
		sum[i]=sum[i-1]+phi[i];
	}
	int n;
	cin>>n;
	if(n==1){//特判
		cout<<0<<endl;
	}
	else{
		cout<<2*sum[n-1]+1<<endl;
	}
	
	return 0;
}

馬上就是,用莫比烏斯反演的程式碼:

點選檢視程式碼
#include<bits/stdc++.h>
using namespace std;
#define il inline
#define re register
#define debug printf("Now is Line : %d\n",__LINE__)
#define file(a) freopen(#a".in","r",stdin);freopen(#a".out","w",stdout)
#define mod 1000000007
il int read()
{
    re int x=0,f=1;re char c=getchar();
    while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
    while(c>='0'&&c<='9') x=x*10+c-48,c=getchar();
    return x*f;
}
#define maxn 40000
int mu[maxn+5],prim[maxn+5],vis[maxn+5],cnt,n,ans;
signed main()
{
    n=read()-1;
    if(!n) return puts("0"),0;
    mu[1]=1;
    for(re int i=2;i<=n;++i)
    {
        if(!vis[i]) prim[++cnt]=i,mu[i]=-1;
        for(re int j=1;j<=cnt;++j)
        {
            if(prim[j]*i>n) break;
            vis[prim[j]*i]=1;
            if(i%prim[j]==0) break;
            mu[i*prim[j]]=-mu[i];//定義求μ
        }
    }	
	for(re int i=1;i<=n;++i) mu[i]+=mu[i-1];
	for(re int l=1,r;l<=n;l=r+1)//分塊
	{
		r=n/(n/l);
		ans+=(mu[r]-mu[l-1])*(n/l)*(n/r);
	}
	printf("%d",ans+2);//正上方和正右方的兩點
    return 0;
}