UOJ221 循环之美 【莫比乌斯反演】【分块】【杜教筛】

题目链接:http://uoj.ac/problem/221

题意:求有多少对 ( x , y ) (x,y) (x,y),满足 1 ≤ x ≤ n 1 \le x \le n 1xn, 1 ≤ y ≤ m 1 \le y \le m 1ym x y x\over y yx k k k进制下是纯循环小数,且 x y x\over y yx数值上两两不同

题解:
有一个很重要的结论: x y x\over y yx k k k进制下为纯循环小数当且仅当 g c d ( y , k ) = = 1 gcd(y,k)==1 gcd(y,k)==1,证明见 https://www.cnblogs.com/lcf-2000/p/6250330.html
所以 a n s = Σ x = 1 n Σ y = 1 m [ g c d ( x , y ) = = 1 ] [ g c d ( y , k ) = = 1 ] ans=\Sigma_{x=1}^n\Sigma_{y=1}^m[gcd(x,y)==1][gcd(y,k)==1] ans=Σx=1nΣy=1m[gcd(x,y)==1][gcd(y,k)==1]
[ g c d ( x , y ) = = 1 ] [gcd(x,y)==1] [gcd(x,y)==1]部分反演可得
a n s = Σ x = 1 n Σ y = 1 m [ g c d ( y , k ) = = 1 ] Σ d ∣ x , d ∣ y μ ( d ) ans=\Sigma_{x=1}^n\Sigma_{y=1}^m[gcd(y,k)==1]\Sigma_{d|x,d|y}\mu(d) ans=Σx=1nΣy=1m[gcd(y,k)==1]Σdx,dyμ(d)
= Σ d = 1 m i n ( n , m ) μ ( d ) [ g c d ( d , k ) = = 1 ] ⌊ n d ⌋ Σ y = 1 ⌊ m d ⌋ [ g c d ( y , k ) = = 1 ] =\Sigma_{d=1}^{min(n,m)}\mu(d)[gcd(d,k)==1]{\lfloor}{n \over d}{\rfloor}\Sigma_{y=1}^{{\lfloor}{m\over d}{\rfloor}}[gcd(y,k)==1] =Σd=1min(n,m)μ(d)[gcd(d,k)==1]dnΣy=1dm[gcd(y,k)==1]
f ( n , k ) = Σ i = 1 n [ g c d ( n , k ) = = 1 ] f(n,k)=\Sigma_{i=1}^n[gcd(n,k)==1] f(n,k)=Σi=1n[gcd(n,k)==1],那么显然
f ( n , k ) = ⌊ n k ⌋ f ( k , k ) + f ( n m o d    k , k ) f(n,k)={\lfloor}{n\over k}{\rfloor}f(k,k)+f(n\mod k,k) f(n,k)=knf(k,k)+f(nmodk,k)
f ( n , k ) ( n ≤ k ) f(n,k)(n\le k) f(n,k)(nk)可以O(k)预处理,对于每次询问就是 O ( n l o g n ) O(nlogn) O(nlogn)(gcd带一个log),可以获得84pts
84pts代码(写起来很简单):

// by Balloons
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mpr make_pair
#define debug() puts("okkkkkkkk")
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)

using namespace std;

typedef long long LL;

const int inf = 1 << 30;
const int maxn=2e7+5;

int n,m,k;
int notpm[maxn],pm[maxn],pcnt=0,f[maxn];
int mu[maxn];

void xxs(){
	notpm[1]=1;mu[1]=1;
	for(int i=2;i<=maxn-5;i++){
		if(!notpm[i]){
			pm[++pcnt]=i;
			mu[i]=-1;
		}
		for(int j=1;j<=pcnt && i*pm[j]<=maxn-5;j++){
			notpm[i*pm[j]]=1;
			if(i%pm[j]==0){
				mu[i*pm[j]]=0;
				break;
			}
			mu[i*pm[j]]=-mu[i];
		}
	}
}

int gcd(int a,int b){return !b?a:gcd(b,a%b);}

int main(){
	scanf("%d%d%d",&n,&m,&k);
	xxs();
	for(int i=1;i<=k;i++){
		f[i]=f[i-1]+(gcd(i,k)==1);
	}
	LL ans=0;
	for(int i=1;i<=n;i++){
		if(gcd(i,k)!=1)continue;
		ans+=1ll * mu[i] * n/i * ((m/i)/k * f[k] + f[m/i%k]);
	}
	printf("%lld\n",ans);

	return 0;
}

继续考虑优化式子:
g ( n , k ) = Σ d = 1 n μ ( d ) [ g c d ( d , k ) = = 1 ] g(n,k)=\Sigma_{d=1}^{n}\mu(d)[gcd(d,k)==1] g(n,k)=Σd=1nμ(d)[gcd(d,k)==1]
考虑 k k k的一个质因子 q q q,则显然 k = p c ∗ q   ( ( q , k ) = = 1 ) k=p^c*q \ ((q,k)==1) k=pcq ((q,k)==1)
因为 [ 1 , n ] [1,n] [1,n]中与 k k k互质的数才能加入 g g g中,所以考虑如何求出与 k k k互质的数,显然等价于求 p , q p,q p,q k k k互质的数
容斥原理易得答案即为 ( x , q ) = = 1 (x,q)==1 (x,q)==1的个数减去 ( x , p ) ≠ 1 (x,p) \neq 1 (x,p)̸=1的个数 1 ≤ x ≤ n 1\le x\le n 1xn
因为与 q q q互质的数可以写成 p c y   ( ( y , q ) = = 1 , ( p , q ) = = 1 ) p^cy \ ((y,q)==1,(p,q)==1) pcy ((y,q)==1,(p,q)==1)的形式,而若 c ≥ 2 c\ge2 c2,那么 μ ( p c y ) \mu(p^cy) μ(pcy)一定为0,不加入答案,所以 c = = 1 c==1 c==1,即所有与 q q q互质的数一定可以写成 p y py py的形式,根据上面的容斥原理我们可以列出一个式子:
g ( n , k ) = Σ i = 1 n [ g c d ( i , q ) = = 1 ] μ ( i ) − Σ y = 1 ⌊ n p ⌋ [ ( g c d ( p y , q ) = = 1 ] μ ( p y ) g(n,k)=\Sigma_{i=1}^n[gcd(i,q)==1]\mu(i)-\Sigma_{y=1}^{{\lfloor}{n\over p}{\rfloor}}[(gcd(py,q)==1]\mu(py) g(n,k)=Σi=1n[gcd(i,q)==1]μ(i)Σy=1pn[(gcd(py,q)==1]μ(py)
= g ( n , q ) − Σ y = 1 ⌊ n p ⌋ [ g c d ( y , q ) = = 1 ] μ ( p y ) =g(n,q)-\Sigma_{y=1}^{{\lfloor}{n\over p}{\rfloor}}[gcd(y,q)==1]\mu(py) =g(n,q)Σy=1pn[gcd(y,q)==1]μ(py)
根据积性函数性质 μ ( p y ) = μ ( p ) μ ( y ) [ g c d ( p , y ) = = 1 ] \mu(py)=\mu(p)\mu(y)[gcd(p,y)==1] μ(py)=μ(p)μ(y)[gcd(p,y)==1]
所以原式 g ( n , k ) = g ( n , q ) − Σ y = 1 ⌊ n p ⌋ [ g c d ( y , p ) = = 1 ] [ g c d ( y , q ) = = 1 ] μ ( p ) μ ( y ) g(n,k)=g(n,q)- \Sigma_{y=1}^{\lfloor\frac{n}{p}\rfloor}[gcd(y,p)==1][gcd(y,q)==1]\mu(p)\mu(y) g(n,k)=g(n,q)Σy=1pn[gcd(y,p)==1][gcd(y,q)==1]μ(p)μ(y)
= g ( n , q ) − μ ( p ) Σ y = 1 ⌊ n p ⌋ [ y ⊥ k ] μ ( y ) =g(n,q)-\mu(p)\Sigma_{y=1}^{\lfloor\frac{n}{p}\rfloor}[y\perp k]\mu(y) =g(n,q)μ(p)Σy=1pn[yk]μ(y)
= g ( n , q ) + g ( ⌊ n p ⌋ , k ) =g(n,q)+g(\lfloor\frac{n}{p}\rfloor,k) =g(n,q)+g(pn,k)
f , g f,g f,g均可以递归求解
为了方便, g ( n , k ) g(n,k) g(n,k)在求解的过程中 k k k代表的是剩余的质因数个数
g g g递归的过程中是用了类似前向星的方式建立分层关系的

// by Balloons
#include <cstdio>
#include <cstring>
#include <map>
#include <iostream>
#include <algorithm>
#define mpr make_pair
#define debug() puts("okkkkkkkk")
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)

using namespace std;

typedef long long LL;
#define int LL

const int inf = 1 << 30,mod=100007;
const int maxn=5e6+5;	// 1e7+5??

int notpm[maxn],pm[maxn],pcnt=0,f[maxn];
int n,m,k;
int mu[maxn],pz[maxn],yz;
int hd[11][mod+1],nxt[mod+1],cur[maxn],clo=0,curres[maxn];

int gcd(int a,int b){return !b?a:gcd(b,a%b);}

void xxs(){
	notpm[1]=mu[1]=1;
	for(int i=2;i<=maxn-5;i++){
		if(!notpm[i]){
			pm[++pcnt]=i;
			mu[i]=-1;
		}
		for(int j=1;j<=pcnt && pm[j]*i<=maxn-5;j++){
			notpm[i*pm[j]]=1;
			if(i%pm[j]==0){
				mu[i*pm[j]]=0;
				break;
			}
			mu[i*pm[j]]=-mu[i];
		}
	}
	for(int i=1;i<=maxn-5;i++)mu[i]+=mu[i-1];
	for(int i=1;i<=maxn-5;i++)f[i]=f[i-1]+(gcd(i,k)==1);
}

int gf(int x){	// f[x]
	return x/k*f[k]+f[x%k];
}

map<int,int>exi;
int djs(int x){	// 杜教筛求莫比乌斯函数前缀和 
	if(x <= maxn-5)return mu[x];
	if(exi[x])return exi[x];
	
	int ans=1;
	for(int i=2,j=0;i<=x;i=j+1){
		j=x/(x/i);
		ans-=(j-i+1)*djs(x/i);
	}
	exi[x]=ans;
	return exi[x];
}

map<int,int>hs;
int g(int y,int x){	// g(y,x)=sum_{i=1}^y [(i,x')==1]mu(i)  x 表示x'剩下的因子数而不是原数x'
	if(!x){
		int tmp=djs(y);	 // 边界情况 mu的前缀和,杜教筛优化 
//		printf("hello %d\n",tmp);
		return tmp;
	}
	if(y<=1)return y;
	
	// 对于每个 x y 分开讨论,一开始写的map一直不对 
	for(int i=hd[x][y%mod];i;i=nxt[i])if(cur[i]==y)return curres[i];	// 类似前向星遍历 
	int now=++clo;cur[clo]=y;nxt[clo]=hd[x][y%mod];hd[x][y%mod]=clo;	// 存边 
	int tmp=g(y,x-1)+g(y/pz[x],x);
//	printf("%d\n",tmp);
	return curres[now]=tmp;	// 记忆化 
}

signed main(){
	scanf("%lld%lld%lld",&n,&m,&k);
	xxs();
	for(int i=1;pm[i]<=k;i++)if(k%pm[i]==0)pz[++yz]=pm[i];	// 求出 k 的所有质因数 
	int ans=0;
	for(int i=1,j=0;i<=n && i<=m;i=j+1){
		j=min(n/(n/i),m/(m/i));
		ans+=(g(j,yz)-g(i-1,yz))*(n/i)*gf(m/i);	// 根据推的式子分块 
//		printf("%d %d %d %d\n",ans,g(yz,j),g(yz,i-1),gf(m/i));
	}
	printf("%lld\n",ans);

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值