[BZOJ 2301] Problem B
Link:
https://www.lydsy.com/JudgeOnline/problem.php?id=2301
Algorithm:
令$g(n,m)$表示在$1<=x<=n,1<=y<=m$满足$gcd(x,y)$是$k$的$(x,y)$的对数。
那么由容斥原理可得$ans=g(b,d)+g(a−1,c-1)−g(a-1,d)-g(b,c−1,k)$
满足$gcd(x,y)$是$k$的$(x,y)$的对数也等价于$(x,y)$互质的对数($1\le x\le n/k,1\le y\le m/k$),即
$g(n,m,k)=g(n/k,m/k,1)$
令$f(i)$表示满足$gcd(x,y)=i$时$(x,y)$的对数
$F(i)$表示满足$i|gcd(x,y)的(x,y)$的对数,则$F(i)=\lfloor n_i \rfloor \lfloor m_i \rfloor$。
于是我们发现这是一个具有倍数关系的莫比乌斯反演:
$F(i)=\sum_{i | d} f(d)$ <-> $f(i)=\sum_{i|d} miu(d/i)*(n/d)*(m/d)$
接下来就要优化每次求f(i)的复杂度,需要将其降至$O(logN)$,
发现$[n/d]$最多有$2sqrt(n)$个取值,那么 $(n/d)*(m/d)$就至多有$2sqrt(n)+2sqrt(m)$个取值
(并不是$*$,对于每个$d \in n/(sqrt(n)+1)<d\le n $, $(n/d)*(m/d)$仍只有一个值)
于是我们发现会有连续的一段$(n/d)*(m/d)$的值相同,便可以使用分块来优化
注意其中转移到下一块的处理技巧:$i=x/(x/i)+1$
个人认为$x/(x/i)$表示根据当前$x/i$的值找出最大的$i′$,再加一就是另一个$x/i$值了
Code:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int MAXN=5e4+10; inline ll read() { char ch;ll num,f=0; while(!isdigit(ch=getchar())) f|=(ch=='-'); num=ch-'0'; while(isdigit(ch=getchar())) num=num*10+ch-'0'; return f?-num:num; } int a,b,c,d,k; int sum[MAXN],mo[MAXN],pri[MAXN],cnt; bool mark[MAXN]; void getmo() { mo[1]=1; for(int i=2;i<=5e4;i++) { if(!mark[i]){mo[i]=-1;pri[++cnt]=i;} for(int j=1;j<=cnt && i*pri[j]<=5e4;j++) { mark[i*pri[j]]=1; if(i%pri[j]==0){mo[i*pri[j]]=0;break;} else mo[i*pri[j]]=-mo[i]; } } for(int i=1;i<=5e4;i++) sum[i]=sum[i-1]+mo[i]; } int cal(int n,int m) { n/=k;m/=k; if(n>m)swap(n,m); int ret=0,pos; for(int i=1;i<=n;i=pos+1) { pos=min(n/(n/i),m/(m/i)); ret+=(sum[pos]-sum[i-1])*(n/i)*(m/i); } return ret; } int main() { getmo(); int T=read(); while(T--) { a=read();b=read();c=read();d=read();k=read(); int res=cal(a-1,c-1)+cal(b,d)-cal(a-1,d)-cal(b,c-1); printf("%d\n",res); } return 0; }
Review:
1、 将gcd(x,y)=k这样的条件转化为x/k与y/k互质
方便使用欧拉函数或莫比乌斯反演解题
2、使用分块对莫比乌斯反演的优化