Luogu P2158 仪仗队【莫比乌斯反演】【线性筛】

2021.11.7 更新
刚刚看了朱哥的博客感觉自己的博客好水呀!赶紧鬼过来继续水博客!!

—————————————分割线—————————————

前言

传送门
蒟蒻又来水博客了!!!
昨天听冯巨讲解了莫比乌斯反演+线性筛法,马上来写一道模板题;
首先分析题意,我们用脑子推一下就知道了答案(之前写的分析,我无言以对)

更:

分析题意,我们要求出小哥哥在四边形的角落上能看到的人数。
由合理的分析+思考(看标签),我们得出了每一个朱哥可以看到的人所在的点坐标有着一种说不清道不明的特性。

这究竟是什么呢??我们来慢慢看:

首先,一个点被看见的前提是他不会被其它的点遮挡,即在它与角落所连的直线上不能有其他的点,而朱哥所在的点坐标设为\((0,0)\),那么遮住它的点的横纵坐标是它的横纵坐标的因数。

分析到这来,我们就知道这个特性就是横纵坐标互质!所以我们就建立直角坐标系,朱哥所在的点为原点,坐标为\((0,0)\)

然后,有关该题的解法,有两种,欧拉函数或者莫比乌斯反演。
欧拉函数\(\phi(n)\)的定义就是在不大于n的正整数中与n互质的数之和,刚好可以用来做这道题。

欧拉函数不用推导,但是是\(O(n)\)的时间复杂度。
但是莫比乌斯+分块可以\(O(\sqrt{n})\)水过去。
放一个线性筛求欧拉函数的模板:

点击查看代码
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]];
		}
	}
}

方程式演算:

\[\begin{aligned} Ans & =\sum_{x=0}^{n-1}\sum_{y=0}^{x-1}[gcd(x,y)=1]+1+\sum_{x=0}^{n-1}\sum_{y=x+1}^{n-1}[gcd(x,y)=1]\\ & =2*\sum_{x=0}^{n-1}\sum_{y=0}^{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}[gcd(x,y)=1]+2(这个2是(1,0)和(0,1)两个特殊点)\\ & =\sum_{x=1}^{n-1}\sum_{y=1}^{n-1}\sum_{d|i,d|j}\mu(d)+2\\ & =\sum_{d=1}^n*\mu(d)\sum_{i=1}^{n/d}\sum_{j=1}^{m/d}1+2\\ & =\sum_{d=1}^{n}\mu(d)*{\frac{n}{d}}*{\frac{m}{d}}+2\\ \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;
}

好的,以上就是该题的两种解法,你学会了吗??\(QWQ\)

同学们下期再见~~~~

posted @ 2021-11-06 20:38  SSZX_loser_lcy  阅读(43)  评论(0编辑  收藏  举报