HDU---4746:Mophues【莫比乌斯反演】

题目:

HDU - 4746

题意:

定义 f(x) 表示 x 的素因子的个数 (包括相同的),如果 f(x) <= p,则称 x 是 p 的幸运数;Q 次询问,每次给出 N,M,P,求有多少对 (a,b) 使得 gcd(a,b) 是 p 的幸运数,并且 1=< a <= N,1 =< b <= M

分析:

题目所求等价于(假设 n <= m):

\sum_{i=1}^{n}\sum_{j=1}^{m}[f(gcd(i,j))\leq p]

=\sum_{g=1}^{n}[f(g)\leq p]\sum_{i=1}^{\left \lfloor \frac{n}{g} \right \rfloor}\sum_{j=1}^{\left \lfloor \frac{m}{g} \right \rfloor}[gcd(i,j)==1]

=\sum_{g=1}^{n}[f(g)\leq p]\sum_{i=1}^{\left \lfloor \frac{n}{g} \right \rfloor}\sum_{j=1}^{\left \lfloor \frac{m}{g} \right \rfloor}\sum_{d|gcd(i,j)}\mu(d)

=\sum_{g=1}^{n}[f(g)\leq p]\sum_{d=1}^{\left \lfloor \frac{n}{g} \right \rfloor}\sum_{i=1}^{\left \lfloor \frac{n}{gd} \right \rfloor}\sum_{j=1}^{\left \lfloor \frac{m}{gd} \right \rfloor}\mu(d)

=\sum_{T=1}^{n}\sum_{g|T}[f(g)\leq p]\mu(\frac{T}{g})\sum_{i=1}^{\left \lfloor \frac{n}{T} \right \rfloor}\sum_{i=1}^{\left \lfloor \frac{m}{T} \right \rfloor}1

=\sum_{T=1}^{n}S(T)\sum_{i=1}^{\left \lfloor \frac{n}{T} \right \rfloor}\sum_{i=1}^{\left \lfloor \frac{m}{T} \right \rfloor}1

最后是令 T = gd 化简而来;发现后面一堆可以 O(1) 直接计算,又是向下取整,只要知道 S(x) 函数的前缀和就可以分块加速计算;首先,p >= 20 时,那么最后答案一定是 N*M (因为 5e5 范围最多 20 个素因子);考虑如何计算 p < 20 时,S(x) 的前缀和:

设 G[x][k] 表示 p == k 时,S(x) 的值;设 sum[x][k] 表示 p == k 时,S(x) 的前缀和

g[x][p] 表示下式的值:

\sum_{d|x}[f(d)== p]\mu(\frac{x}{d})

那么我们可以预处理出 g[x][p],再对 g[x][p] 求一遍前缀和,得到 G[x][p]; 再对 G[x][p] 求一遍前缀和即可得到 sum[x][p] 

考虑如何预处理 g[x][p],先把 5e5 范围内的素数筛出来,然后用这些素数去分解 x 得到 f(x)【这样比暴力分解要快】,然后用埃式筛的思想得到每个数因子即可求出 g[x][p]

预处理的复杂度为 O(NlogN),每次询问的复杂度为 O(根号N)

代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int maxn = 5e5+15;
int prime[maxn],mul[maxn],cnt,vis[maxn],f[maxn];
LL sum[maxn][20],g[maxn][20];
int cal(int x){                           
	int num = 0;
	for(int i = 0; i<cnt&&1ll*prime[i]*prime[i]<=x; ++i){
		while(x%prime[i] == 0){
			num++; x /= prime[i];
		}
	}
	return x > 1 ? num+1 : num;
}
void init(){
	mul[1] = 1;
	for(int i = 2;i < maxn; ++i){
		if(!vis[i]){
			mul[i] = -1; prime[cnt++] = i;
		}
		for(int j=0;j<cnt&&1ll*i*prime[j]<maxn; ++j){
			int x = i*prime[j]; vis[x] = 1;
			if(i%prime[j] == 0){ mul[x] = 0; break; }
			else mul[x] = -mul[i];
		}
	}
	for(int i = 2;i < maxn; ++i) f[i] = cal(i);    // 计算f[x]
	for(int i = 1;i < maxn; ++i){                  // 计算g[x][p]
		for(int j = i;j < maxn; j+=i){
			g[j][f[i]] += mul[j/i];
		}
	}
	for(int i = 1;i < maxn; ++i){                  // 计算sum[x][p]
		sum[i][0] = sum[i-1][0]+g[i][0];
		for(int j = 1;j < 20; ++j){
			g[i][j] += g[i][j-1];
			sum[i][j] = sum[i-1][j] + g[i][j];
		}
	}
}
LL solve(int n,int m,int p){                       // 分块加速计算 
	if(p >= 20) return 1ll*n*m;
	if(n > m) swap(n,m);
	LL res = 0;
	for(int i = 1,last;i <= n;i = last+1){          
		last = min(n/(n/i),m/(m/i));
		res += (sum[last][p]-sum[i-1][p])*(n/i)*(m/i);
	}
	return res;
}
int main(){
	init();
	int n,m,p,q; scanf("%d",&q);
	while(q--){
		scanf("%d %d %d",&n,&m,&p);
		printf("%I64d\n",solve(n,m,p));
	}
	return 0;
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值