莫比莫斯小结
前几天多校的时候碰到一道莫比乌斯的题目,一脸懵逼,然后恶补了几道。
先看下莫比乌斯的基本概念:
定理:和是定义在非负整数集合上的两个函数,并且满足条件,那么我们得到结论
在上面的公式中有一个函数,它的定义如下:
(1)若,那么
(2)若,均为互异素数,那么
(3)其它情况下
来自acdreamer大佬的博客。
先说下第一道入门题目(虽然入门,处理的东西也不少。。):
hdu 1695:http://acm.hdu.edu.cn/showproblem.php?pid=1695
题目意思:对于1<=x<=d,1<=y<=d 求gcd(x,y)=k的个数
题解:首先第一步,对于对于1<=x<=d,1<=y<=d 如果 gcd(x,y)=k,那么对于1<=x/k<=d/k,1<=y/k<=d/k gcd(x,y)=1。问题就转化为对于1<=x<=d/k,1<=y<=d/k 求gcd(x,y)=1的个数。
然后我们定义两个函数。F(n)与f(n);
F(d)为 有多少对(x,y)满足 gcd(x,y)== d 的倍数 。
f(d)为有多少对(x,y)满足 gcd(x,y)== d 。
我们要求的是f(d)的值,但我们发现F(d)函数比较容易求解F(d)=(n/d)*(m/d);(这个怎么理解呢,我们把范围1~n里面能够整除x的数的个数表示为n/x;对于两个数,如果两个数的因子都有d,那么这两个数的倍数的gcd一定是d的倍数)。
是不是可以套进去?
而且我们简化之后,要求的n为1 那么u也就很好求了。
贴一个线性求mu的代码:
int prime[100001]; int mu[100001]; int sum[100001]; void init() { int vis[100001]; memset(vis,0,sizeof(vis)); memset(mu,0,sizeof(mu)); memset(prime,0,sizeof(prime)); mu[1]=1; int ret=0; for(int i=2;i<=100000;i++) { if(!vis[i]) { prime[ret++]=i; mu[i]=-1; } for(int j=0;j<ret && i*prime[j] < 100000;j++) { int temp=i*prime[j]; vis[temp]=1; if(i%prime[j]) mu[temp]=-mu[i]; else { mu[temp]=0; break; } }
}
有了这些之后,还有一个问题。题目要求去重,这个怎么理解呢?
此时,走到这一步,我们已经求得了(x,y)满足 gcd(x,y)=1 的对数 ,但题目中说明了,(1,2)和(2,1)算一种情况,那么我们就要减去多余了的情况,怎那么找出那些多算进去的情况呢? 下面的图画的很清楚:
G(b,b)就是多算进去的这些情况,
那么 G(b,d)- G(b,b)/ 2 就是最终我们要求的结果了,至于这一点,有不懂的请在纸上画一画,这不是我要讲的重点了。(转自http://blog.csdn.net/lixuepeng_001/article/details/50577932)
ac代码:
#include <iostream> #include <cstdio> #include <cstring> using namespace std; const int maxn=1e5+7; bool vis[maxn]; int prime[maxn],mu[maxn]; int cnt; void Init(){ int N=maxn; memset(prime,0,sizeof(prime)); memset(mu,0,sizeof(mu)); memset(vis,0,sizeof(vis)); mu[1] = 1; cnt = 0; for(int i=2; i<N; i++){ if(!vis[i]){ prime[cnt++] = i; mu[i] = -1; } for(int j=0; j<cnt&&i*prime[j]<N; j++){ vis[i*prime[j]] = 1; if(i%prime[j]) mu[i*prime[j]] = -mu[i]; else{ mu[i*prime[j]] = 0; break; } } } } void getMu(){ int N=maxn; for(int i=1;i<N;++i){ int target=i==1?1:0; int delta=target-mu[i]; mu[i]=delta; for(int j=2*i;j<N;j+=i) mu[j]+=delta; } } int main() { ios::sync_with_stdio(false); int a,b,c,d,k; int T,Case=0; Init(); cin>>T; while(T--){ cin>>a>>b>>c>>d>>k; cout<<"Case "<<++Case<<": "; if(k==0){ cout<<"0"<<endl; continue; } b/=k,d/=k; long long ans1=0,ans2=0; for(int i=1;i<=min(b,d);i++){ ans1+=(long long)mu[i]*(b/i)*(d/i); } for(int i=1;i<=min(b,d);i++){ ans2+=(long long)mu[i]*(min(b,d)/i)*(min(b,d)/i); } cout<<ans1-ans2/2<<endl; } return 0; }
对于这个问题还有一个知识点可以,就是分块求和。
对于[n/x]这个表达式,当[n/x]=d是不是有很多x可以对应这个值?
比如a'=100,那么d在[34,50]之间a'/d都是2。
那么我们可以把连续的一段d一起来算(分块):
设a'/d=x,那么最后一个a'/d=x的d=a'/x,所以这段连续的区间就是[d,a'/(a'/d)]
结合b'/d,取个min就可以了。
我们就不需要对每一个x都做一个遍历而是可以分块把复杂度降到根号n;
bzoj1101:
1101: [POI2007]Zap
Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 1400 Solved: 455
[Submit][Status]
Description
FGD正在破解一段密码,他需要回答很多类似的问题:对于给定的整数a,b和d,有多少正整数对x,y,满足x<=a,y<=b,并且gcd(x,y)=d。作为FGD的同学,FGD希望得到你的帮助。
Input
第一行包含一个正整数n,表示一共有n组询问。(1<=n<= 50000)接下来n行,每行表示一个询问,每行三个正整数,分别为a,b,d。(1<=d<=a,b<=50000)
Output
对于每组询问,输出到输出文件zap.out一个正整数,表示满足条件的整数对数。
Sample Input
4 5 2
6 4 3
Sample Output
2
HINT
对于第一组询问,满足条件的整数对有(2,2),(2,4),(4,2)。对于第二组询问,满足条件的整数对有(6,3),(3,3)。
#include <iostream> #include <cstdio> #include <cstring> #include <queue> using namespace std; typedef long long ll; int prime[100001]; int mu[100001]; int sum[100001]; void init() { int vis[100001]; memset(vis,0,sizeof(vis)); memset(mu,0,sizeof(mu)); memset(prime,0,sizeof(prime)); mu[1]=1; int ret=0; for(int i=2;i<=100000;i++) { if(!vis[i]) { prime[ret++]=i; mu[i]=-1; } for(int j=0;j<ret && i*prime[j] < 100000;j++) { int temp=i*prime[j]; vis[temp]=1; if(i%prime[j]) mu[temp]=-mu[i]; else { mu[temp]=0; break; } } } for(int i=1;i<=100001;i++) { sum[i]=sum[i-1]+mu[i]; } } int main() { ios::sync_with_stdio(false); int t; cin>>t; init(); int Case=0; while(t--) { int b,d,k; cin>>b>>d>>k; printf("Case %d: ",++Case); if(k==0) { cout<<"0"<<endl; continue; } ll temp1,temp2; temp1=temp2=0ll; d/=k; b/=k; int pos; for(int i=1;i<=min(b,d);i=pos+1) { pos=min(b/(b/i),d/(d/i));//求出分块区间 temp1+=(sum[pos]-sum[i-1])*(b/i)*(d/i);// 对mu做一个前缀和的预处理 } cout<<temp1<<endl; } return 0; }
好以上都是对于求一定范围内gcd(x,y)=k的情况,那么要求gcd(x,y)为质数的情况呢?
前面求质数的情况已经介绍过了,这里无非就是枚举一次范围内质数的个数,但是直接枚举是会超时的,我们还是要用到分块的思想。既然要用到分块的思想那么我们势必需要对前缀和做一个处理。http://www.cnblogs.com/iwtwiioi/p/4132095.html 这位大佬里面提到了对前缀和的处理,很详细。
未完待续。。。看了将近三天才处理掉一下皮毛。。加油加油