把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

莫比乌斯反演练习归纳

这里主要是讲题目,然后提炼出一些有用的经验。统一钦定 \(n\leq m\)

HDU - 1695 GCD

题意概述

多次询问(大约 \(3000\) 次),每次给出 \(a,b,c,d,k\),求:

\[\sum_{i=a}^b\sum_{j=\max(c,i)}^d[\gcd(i,j)=k] \]

其中,\(1\leq a\leq b\leq 10^5,1\leq c\leq d\leq 10^5,0\leq k\leq 10^5\)

分析

这是一道经典题目,容斥求一下就行了。

转化成求:

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

这个是好求的,容易写成(钦定 \(n<m\)):

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

不难得到:

\[\sum_{p=1}^{\left\lfloor\frac{n}{k}\right\rfloor}\mu(p)\sum_{i=1}^{\left\lfloor\frac{n}{kp}\right\rfloor}\sum_{j=i}^{\left\lfloor\frac{m}{kp}\right\rfloor}1 \]

后面那个是好求的,可以 \(\mathcal{O}(1)\)

然后数论分块即可,或者这道题目直接暴力就可以了,不过需要注意这个 \(k=0\),特判一下就可以了。

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <stdlib.h>
#include <vector>
#define int long long
#define N 100005
using namespace std;
int cnt,prime[N],mu[N];
bool vis[N];
void init(int n) {
	mu[1] = 1;
	for (int i = 2;i <= n;i ++) {
		if (!vis[i]) prime[++cnt] = i,mu[i] = -1;
		for (int j = 1;j <= cnt && prime[j] * i <= n;j ++) {
			vis[i * prime[j]] = 1;
			if (i % prime[j] == 0) {
				mu[i * prime[j]] = 0;
				break;
			}
			mu[i * prime[j]] = -mu[i];
		}
	}
//	for (int i = 1;i <= 10;i ++) cout << mu[i] << ' ';
}
int gcd(int a,int b) {
	return b ? gcd(b,a % b) : a;
}
int T,k;
int calc(int n,int m) {
	if (k == 0) return 0; 
	if (n > m) swap(n,m);//n<=m
	int res = 0;
//	for (int i = 1;i <= n / k;i ++)
//		for (int j = i;j <= m / k;j ++)
//			res += (gcd(i,j) == 1);
//	if (k)
	for (int i = 1;i <= n / k;i ++) {
		int tn = n / k,tm = m / k;
		tn /= i,tm /= i;
		res += mu[i] * ((2 * tm - tn + 1) * tn / 2);
	}
	return res;
}
signed main(){
	init(1e5);
	cin >> T;
	for (int cntt = 1;cntt <= T;cntt ++) {
		int a,b,c,d;
		scanf("%lld%lld%lld%lld%lld",&a,&b,&c,&d,&k);
//		int res = 0;
//		for (int i = a;i <= b;i ++)
//			for (int j = max(c,i);j <= d;j ++) res += (gcd(i,j) == k);
//		printf("%lld\n",res); 
		printf("Case %lld: %lld\n",cntt,calc(b,d) - calc(a - 1,d) - calc(b,c - 1) + calc(a - 1,c - 1));
	}
	return 0;
}

HDU - 4746 Mophues

题目概述

设一个数为 \(x=\prod_i p_i\),如果 \(x\) 分解出来的最多质因数的个数为 \(k\) 且不大于 \(P\) 则称此 \(x\)\(P\) 幸运数。

\(Q\) 次询问,每次询问给出 \(n,m,P\),并定义 \(f(x)=k\)(含义见上),求出:

\[\sum_{a=1}^n\sum_{b=1}^m[f(\gcd(a,b))\leq P] \]

其中 \(1\leq n,m,P\leq 5\times 10^5,1\leq Q\leq 5000\)

分析

显然地:

\[\sum_{a=1}^n\sum_{b=1}^m[f(\gcd(a,b))\leq P] \]

想要将套着的东西拿出来就需要枚举嘛,得到:

\[\sum_{d=1}^n[f(d)\leq P]\sum_{a=1}^n\sum_{b=1}^m[\gcd(a,b)=d] \]

不难得到:

\[\sum_{d=1}^n[f(d)\leq P]\sum_{p=1}^{\left\lfloor\frac{n}d\right\rfloor}\mu(p)\left\lfloor\frac{n}{dp}\right\rfloor\left\lfloor\frac{m}{dp}\right\rfloor \]

套路地令 \(T=dp\) 则:

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

\(T\) 提到前面:

\[\sum_{T=1}^n\left\lfloor\frac n T\right\rfloor\left\lfloor\frac m T\right\rfloor\left(\sum_{d\mid T}[f(d)\leq P]\mu(\frac t d)\right) \]

\(g_P(t)=\sum_{d\mid T}[f(d)\leq P]\mu(\frac t d)\)

考虑可以按 \(P\) 升序处理询问,考虑如何 \(g_P(t)\rightarrow g_{P+1}(t)\)

可以有:\(g_{P+1}(t)=g_{P}(t)+\sum_{d\mid t}\mu(\frac t d)[f(d)=P+1]\)

考虑到用的是尺取法,而且每个数字对应唯一一个 \(f(x)\),且每个数字最多只会被用一次,所以说,这里复杂度总共为 \(\mathcal{O}(n\log n)\)

如何加这个 \(\mu(x)\) 呢?直接存几个桶 \(rtj_x\) 表示满足 \(f(y)=x\)\(y\) 组成的集合,然后直接枚举再枚举倍数就可以了。

但是我们求答案的时候是前缀和,所以说我们这种散点求和不好维护,因此我们使用单点修改,区间查询的树状数组就好了。

于是这题就做完了。

代码

时间复杂度 \(\mathcal{O}(n\log^2 n)\)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <stdlib.h>
#include <algorithm>
#include <vector>
#define int long long
#define N 500005
using namespace std;
int gcd(int a,int b) {
	return b ? gcd(b,a % b) : a;
}
int cnt,prime[N],pri[N],mu[N],tj[N];
vector<int> rtj[25];
int ans[N];
bool vis[N];
void init(int n){
	mu[1] = 1;
	for (int i = 2;i <= n;i ++) {
		if (!vis[i]) prime[++cnt] = i,mu[i] = -1;
		for (int j = 1;j <= cnt && prime[j] * i <= n;j ++) {
			vis[i * prime[j]] = 1;
			pri[i * prime[j]] = prime[j];
			if (i % prime[j] == 0) break;
			mu[i * prime[j]] = -mu[i];
		}
	}
	for (int i = 2;i <= n;i ++) {
		int p = i;
		tj[i] = 1;
		while(pri[p]) {
			tj[i] ++;
			p /= pri[p];
		}
	}
	for (int i = 1;i <= n;i ++) rtj[tj[i]].push_back(i);
}
struct node{
	int n,m,p,id;
}q[N];
int tr[N];
void update(int x,int val,int n) {
	for (;x <= n;x += x & -x) tr[x] += val;
}
int query(int x) {
	int res = 0;
	for (;x;x -= x & -x) res += tr[x];
	return res;
}
int querysum(int l,int r) {
	return query(r) - query(l - 1);
}
signed main(){
	init(5e5);
	int T;
	cin >> T;
	for (int i = 1;i <= T;i ++) scanf("%lld%lld%lld",&q[i].n,&q[i].m,&q[i].p),q[i].id = i;	
	stable_sort(q + 1,q + 1 + T,[](node x,node y) {
		return x.p < y.p;
	});
	int nowp = -1;
	for (int i = 1;i <= T;i ++) {
		int n = q[i].n,m = q[i].m,qid = q[i].id,qp = q[i].p;
		while(nowp < qp) {
			nowp ++;
			for (auto j : rtj[nowp])
				for (int t = j;t < N;t += j) update(t,mu[t / j],N - 1);
		}
		if (n > m) swap(n,m);
		int res = 0;
		for (int l = 1,r;l <= n;l = r + 1) {
			r = min(n / (n / l),m / (m / l));
			int ret = 0;
//			for (int j = l;j <= r;j ++) ret += g[j];
			res += querysum(l,r) * (n / l) * (m / l); 
		}
		ans[qid] = res;
	}
	for (int i = 1;i <= T;i ++) printf("%lld\n",ans[i]);
	return 0;
} 

HDU - 4675 GCD of Sequence

题目概述

给出 \(n\) 个元素 \(a_1,a_2,\dots,a_n\)\(m\),并满足 \(a_i\in[1,m]\)

询问有多少个序列 \(b\) 满足:

  • \(1\leq b_i\leq m\)
  • \(\gcd\{b_n\}=d\)
  • 恰好有 \(k\) 个位置满足 \(a_i\ne b_i\)

多测。数据范围:\(1\leq n,m\leq 3\times 10^5\)

分析

感觉经典问题,先设 \(f_{i}\) 表示当序列 \(b\)\(\gcd\)\(i\) 的倍数的方案。

现在考虑第 \(3\) 个限制,记 \(cnt_i\) 表示 \(a\) 中有多少数是 \(i\) 的倍数。

那么我们就可以得到 \(f_i\) 的表达式了,首先我们肯定要选择 \(n-k\) 个位置与 \(a\) 相同,那么方案为 \(\binom{cnt_i}{n-k}\),这些只有一个选择,其他位置的选择考虑分成当前位置的数值是否是 \(i\) 的倍数。

如果是 \(i\) 的倍数,那么我们要使得 \(b\) 在这个位置上不同,选择为 \(\left\lfloor\frac{m}i\right\rfloor\),而这样的位置有 \((cnt_i-n+k)\) 个,因此方案为 \(\left(\left\lfloor\frac{m}i-1\right\rfloor\right)^{cnt_i-n+k}\)

如果不是 \(i\) 的倍数,那么选择就不需要减去 \(1\) 了,而这样的位置有 \(n-cnt_i\) 个。

因此:

\[f_i=\binom{cnt_i}{n-k}\left(\left\lfloor\frac{m}i-1\right\rfloor\right)^{cnt_i-n+k}\left(\left\lfloor\frac{m}i\right\rfloor\right)^{n-cnt_i} \]

考虑如何求答案 \(ans_i\) 表示 \(\gcd\) 恰好为 \(i\) 的方案。

那么显然满足:

\[ans_i=f_i-\sum_{j=2i}^m ans_j \]

然后从大到小求 \(ans\) 就可以了。

代码

时间复杂度 \(\mathcal{O}(\sum m\log m)\)

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <stdlib.h>
#include <vector>
#define int long long
#define N 300005
using namespace std;
const int mod = 1e9 + 7;
int qpow(int a,int b) {
	int res = 1;
	while(b) {
		if (b & 1) res = res * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return res;
}
int n,m,k,mp[N],cnt[N],inv[N],jc[N];
int C(int a,int b) {
	if (a < 0 || b < 0 || a < b) return 0;
	return jc[a] * inv[b] % mod * inv[a - b] % mod;
}
int a[N],ans[N];
signed main(){
	jc[0] = jc[1] = inv[0] = inv[1] = 1;
	for (int i = 2;i < N;i ++) jc[i] = jc[i - 1] * i % mod,inv[i] = (mod - mod / i) * inv[mod % i] % mod;
	for (int i = 2;i < N;i ++) inv[i] = inv[i - 1] * inv[i] % mod;
	while(~scanf("%lld%lld%lld",&n,&m,&k)) {
		for (int i = 1;i <= n;i ++) scanf("%lld",&a[i]),mp[a[i]] ++;
		for (int d = 1;d <= m;d ++)
			for (int t = d;t <= m;t = t + d) cnt[d] += mp[t];
		for (int i = m;i;i --) {
			if (cnt[i] >= n - k) ans[i] = C(cnt[i],n - k) * qpow(m / i - 1,cnt[i] - n + k) % mod * qpow(m / i,n - cnt[i]) % mod;//注意条件
			for (int j = i + i;j <= m;j += i) ans[i] -= ans[j],ans[i] = (ans[i] + mod) % mod;
		}
		for (int i = 1;i <= m;i ++) printf("%lld ",ans[i]);
		putchar('\n');
		for (int i = 1;i <= m;i ++) ans[i] = cnt[i] = mp[i] = 0;
	}
	return 0;
} 

拓展

事实上,若 \(f_i\) 表示的是倍数,而 \(F_i\) 表示的是恰好,那么满足:

\[F_i=\sum_{i\mid d}\mu(d)f(\frac d i) \]

这由莫比乌斯反演定理直接得到。

这个相较于上述方法的优势在于可能优化到 \(\mathcal{O}(n)\) 以下。

HDU - 4676 Sum of Gcd

题目概述

给出 \(a\) 以及 \(Q\)\((L,R)\) 求:

\[\sum_{L\leq i<j\leq R}\gcd(a_i,a_j) \]

多测大约 \(10\) 组,数据范围:\(1\leq n,Q \leq2\times 10^4\)

分析

这道题目确实难想。

首先看到完整的 \(\gcd\) 考虑用欧拉函数代替,于是乎:

\[\sum_{L\leq i<j\leq R}\sum_{d\mid \gcd(a_i,a_j)}\varphi(d) \]

考虑提到前面去,将最大公约数转化成约数:

\[\sum_{d=1}^n\varphi(d)\sum_{L\leq i<j\leq R}[d\mid a_i][d\mid a_j] \]

观察后面的式子,可以发现其本质就是要知道含有 \(d\) 这个约数的 \(a_i(i\in[L,R])\) 有多少个,然后任选两个。

于是我们处理出 \(cnt_i\) 表示在当前区间 \([L,R]\) 中,有多少个数能被 \(i\) 整除,那么答案就为:

\[\sum_{d=1}^n\varphi(d)\binom{cnt_d}{2} \]

现在将目光放在询问区间上,发现可以离线,考虑莫队。

当我们增加一个数 \(x\) 时,如果增加了一个约数 \(d\),那么我们想要将 \(\varphi(d)\binom{cnt_d}2\) 变成 \(\varphi(d)\binom{cnt_d+1}2\),而这里只需加一个 \(\varphi(d)\binom{cnt_d}1\) 即可,这是杨辉三角。

少一个数差不多。

于是这道题目就做完了。

代码

分析枚举其因数,考虑均摊,那么 \(\frac 1 n\sum_{i=1}^nd(i)=\frac 1 n\sum_{i=1}^n\frac n i\approx\log n\),两个都可以得到。

于是时间复杂度为 \(\mathcal{O}(n\sqrt n\log n)\)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <stdlib.h>
#include <algorithm>
#include <vector>
#define int long long
#define N 20004
using namespace std;
int cntp,prime[N],phi[N];
vector<int> d[N];
bool vis[N];
void init(int n) {
	phi[1] = 1;
	for (int i = 2;i <= n;i ++) {
		if (!vis[i]) prime[++cntp] = i,phi[i] = i - 1;
		for (int j = 1;j <= cntp && prime[j] * i <= n;j ++) {
			vis[i * prime[j]] = 1;
			if (i % prime[j] == 0) {
				phi[i * prime[j]] = phi[i] * prime[j];
				break;
			}
			phi[i * prime[j]] = (prime[j] - 1) * phi[i];
		}
	}
//	for (int i = 1;i <= 10;i ++) cout << phi[i] << ' ';
	for (int i = 1;i <= n;i ++)
		for (int j = i;j <= n;j += i) d[j].push_back(i);
}
int gcd(int a,int b) {
	return b ? gcd(b,a % b) : a;
}
int T,n,a[N];
struct node{
	int l,r,p,id;
}q[N];
int cnt[N],sum,ans[N];
int m;
void add(int x) {
	x = a[x];
	for (auto i : d[x]) {
		sum += cnt[i] * phi[i];
		cnt[i] ++;
	}
}
void del(int x) {
	x = a[x];
	for (auto i : d[x]) {
		sum -= (cnt[i] - 1) * phi[i];
		cnt[i] --;
	}
}
void solve(){
	scanf("%lld",&n);
	for (int i = 1;i <= n;i ++) scanf("%lld",&a[i]);
	scanf("%lld",&m);
	int len = ceil(sqrt(n));
	for (int i = 1;i <= m;i ++) scanf("%lld%lld",&q[i].l,&q[i].r),q[i].p = q[i].l / len,q[i].id = i;
	stable_sort(q + 1,q + 1 + m,[](node x,node y) {
		if (x.p == y.p) return (x.p & 1) ? x.r > y.r : x.r < y.r;
		return x.p < y.p;
	});
	sum = 0;
	for (int i = 1;i <= n;i ++) cnt[i] = 0;
	int l = 1,r = 0;
	for (int i = 1;i <= m;i ++) {
		int ql = q[i].l,qr = q[i].r,qid = q[i].id;
		while(l < ql) del(l ++);
		while(l > ql) add(-- l);
		while(r < qr) add(++ r);
		while(r > qr) del(r --);
		ans[qid] = sum;
	}
	for (int i = 1;i <= m;i ++) printf("%lld\n",ans[i]);
}
signed main(){
	init(2e4); 
	cin >> T;
	for (int cntt = 1;cntt <= T;cntt ++) {
		printf("Case #%lld:\n",cntt);
		solve();
	}
	return 0;
}

P2257 YY的GCD

题目概述

给定 \(N, M\),求 \(1 \leq x \leq N\)\(1 \leq y \leq M\)\(\gcd(x, y)\) 为质数的 \((x, y)\) 有多少对。

数据范围:\(T=10^4,N,M\leq 10^7\)

分析

经典题目,简单写写。

考虑枚举质数,转化成艾佛森括号形式从而莫比乌斯反演。

于是乎:

\[\sum_{P\in prime}\sum_{i=1}^n\sum_{j=1^m}[\gcd(x,y)=P] \]

不难得到:

\[\sum_{P\in prime}\sum_{d=1}^{\left\lfloor\frac n P\right\rfloor}\mu(d)\left\lfloor\frac n{dP}\right\rfloor\left\lfloor\frac m {dP}\right\rfloor \]

套路地令 \(T=dP\) 则:

\[\sum_{T=1}^n\left\lfloor\frac n{T}\right\rfloor\left\lfloor\frac m {T}\right\rfloor\sum_{P\mid T,P\in prime}\mu(\frac T P) \]

后面那个可以预处理,然后整除分块就行了。

代码

时间复杂度 \(\mathcal{O}(n+T\sqrt n)\)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <stdlib.h>
#include <vector>
#define int long long
#define N 10000007
#define M 5000006
using namespace std;
bool vis[N];
int prime[M],mu[N],cnt,g[N];
void init(int n){
	mu[1] = 1;
	for (int i = 2;i <= n;i ++) {
		if (!vis[i]) prime[++cnt] = i,mu[i] = -1;
		for (int j = 1;j <= cnt && prime[j] * i <= n;j ++) {
			vis[i * prime[j]] = 1;
			if (i % prime[j] == 0) break;
			mu[i * prime[j]] = -mu[i];
		}
	}
	for (int j = 1;j <= cnt;j ++)
		for (int t = prime[j];t <= n;t += prime[j]) g[t] += mu[t / prime[j]];
	for (int i = 1;i <= n;i ++) g[i] += g[i - 1];
} 
int T;
int n,m;
signed main(){
	init(1e7);
	cin >> T;
	for (;T--;) {
		scanf("%lld%lld",&n,&m);
		if (n > m) swap(n,m);
		int res = 0;
		for (int l = 1,r;l <= n;l = r + 1) {
			r = min(n / (n / l),m / (m / l));
			res = (res + (n / l) * (m / l) * (g[r] - g[l - 1]));
		}
		printf("%lld\n",res);
	}
	return 0;
}

P3312 [SDOI2014] 数表

题目概述

给定 \(n,m,a\) 求:

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

数据范围:多测,\(1\leq n,m\leq 10^5,1\leq Q\leq 2\times 10^4\)

分析

先不考虑 \(a\) 的限制,因此我们只需要保留 \(\sigma(d)\) 即可,在最后考虑。

套路地:

\[\sum_{d=1}^n\sigma(d)\sum_{i=1}^{\left\lfloor\frac n d\right \rfloor}\sum_{j=1}^{\left\lfloor\frac m d\right\rfloor}[\gcd(i,j)=1] \]

于是有:

\[\sum_{d=1}^n\sigma(d)\sum_{p=1}^{\left\lfloor\frac n d\right\rfloor}\mu(p)c \]

套路地令 \(T=dp\) 则:

\[\sum_{T=1}^n\left\lfloor\frac n {T}\right\rfloor\left\lfloor\frac m {T}\right\rfloor\sum_{d\mid T}\sigma(d)\mu(\frac T d) \]

现在加入 \(a\) 的限制,我们发现对 \(a\) 只需要尺取法即可,跟幸运数那题差不多,还是需要树状数组。

于是这题就做完了。

代码

时间复杂度 \(\mathcal{O}(Q\log^2 n + Q\sqrt n)\)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <stdlib.h>
#include <cstring>
#include <vector>
#define int long long
#define N 100005
using namespace std;
const int mod = 2147483648;
int cnt,prime[N],mu[N],sp[N],sigma[N];
bool vis[N];
struct Node{
	int id,sigma;
}box[N];
void init(int n) {
	mu[1] = sp[1] = sigma[1] = 1;
	for (int i = 2;i <= n;i ++) {
		if (!vis[i]) prime[++cnt] = i,mu[i] = -1,sp[i] = sigma[i] = i + 1;
		for (int j = 1;j <= cnt && prime[j] * i <= n;j ++) {
			vis[i * prime[j]] = 1;
			int t = i * prime[j];
			if (i % prime[j] == 0) {
				mu[t] = 0;
				sp[t] = sp[i] * prime[j] + 1;
				sigma[t] = sigma[i] / sp[i] * sp[t];
				break;
			}
			mu[t] = -mu[i];
			sp[t] = prime[j] + 1;
			sigma[t] = sigma[i] * (prime[j] + 1);
		}
	}
	for (int i = 1;i <= n;i ++) box[i] = (Node){i,sigma[i]};
	stable_sort(box + 1,box + 1 + n,[](Node x,Node y) {
		return x.sigma < y.sigma;
	});
} 
int tr[N];
void update(int x,int val,int n) {
	for (;x <= n;x += x & -x) tr[x] += val;
}
int query(int x) {
	int res = 0;
	for (;x;x -= x & -x) res += tr[x];
	return res;
}
struct node{
	int n,m,a,id;
}q[N];
int ans[N];
signed main(){
	int mxn = 1e5;
	init(1e5);
	int T;
	cin >> T;
	for (int i = 1;i <= T;i ++) scanf("%lld%lld%lld",&q[i].n,&q[i].m,&q[i].a),q[i].id = i;
	stable_sort(q + 1,q + 1 + T,[](node x, node y) {
		return x.a < y.a;
	});
	int pa = 1;
	for (int i = 1;i <= T;i ++) {
		int n = q[i].n,m = q[i].m,qa = q[i].a,qid = q[i].id;
		if (n > m) swap(n,m);
		while(pa <= mxn && box[pa].sigma <= qa) {
			int id = box[pa].id;
			for (int t = id;t <= mxn;t += id) update(t,(mu[t / id] * sigma[id] % mod + mod) % mod,mxn);
			pa ++;
		}
		int res = 0;
		for (int l = 1,r;l <= n;l = r + 1) {
			r = min(n / (n / l),m / (m / l));
			int s = (query(r) - query(l - 1)) % mod;
			s = (s + mod) % mod;
			res = (res + s * (n / l) % mod * (m / l) % mod) % mod;
		}
		ans[qid] = res;
	}
	for (int i = 1;i <= T;i ++) printf("%lld\n",ans[i]);
	return 0;
}

BZOJ - 2693 jzptab

题目概述

求:

\[\sum_{i=1}^n\sum_{j=1}^m\text{lcm}(i,j)\bmod 10^8+9 \]

数据范围:\(T\leq10^4,1\leq n,m\leq 10^7\).

分析

展开:

\[\sum_{i=1}^n\sum_{j=1}^m\frac{ij}{\gcd(i,j)} \]

想要把 \(\gcd\) 提出,显然枚举:

\[\sum_{d=1}^n d\sum_{i=1}^{\left\lfloor\frac n d\right\rfloor}\sum_{j=1}^{\left\lfloor\frac m d\right\rfloor}ij[\gcd(i,j)=1] \]

得到:

\[\sum_{d=1}^n d\sum_{p=1}^{\left\lfloor\frac n d\right\rfloor}\mu(p)p^2S\left(\left\lfloor \frac n{dp}\right\rfloor\right)S\left(\left\lfloor\frac m{dp}\right\rfloor\right) \]

其中 \(S(x)\) 代表 \(\sum_{i=1}^x i\)

套路地令 \(T=dp\) 则:

\[\sum_{T}^n S\left(\left\lfloor\frac n T\right\rfloor\right)S\left(\left\lfloor\frac m T\right\rfloor\right)\sum_{d\mid T}\mu(\frac T d)\frac{T^2}d \]

由对称性化简得到:

\[\sum_{T}^n S\left(\left\lfloor\frac n T\right\rfloor\right)S\left(\left\lfloor\frac m T\right\rfloor\right)\sum_{d\mid T}\mu(d)Td \]

\(g(T)=T\sum_{d\mid T}\mu(d)d\),不难发现是一个积性函数。

证明:
\(a,b\) 互质则:

\[g(a)=a\sum_{d\mid a}\mu(d)d,g(b)=b\sum_{d\mid b}\mu(d)d \]

而:

\[g(ab)=ab\sum_{d\mid ab}\mu(d)d=ab\sum_{d_1\mid a}\sum_{d_2\mid b}\mu(d_1d_2)d_1d_2 \]

于是:

\[g(ab)=ab\sum_{d_1\mid a}\mu(d_1)d_1\sum_{d_2\mid b}\mu(d_2)d_2=g(a)g(b) \]

得证。

知道:\(g(p)=p-p^2,g(p^k)p=g(p^{k+1})\),因此可以直接线性筛。

于是这道题目做完了。

代码

时间复杂度 \(\mathcal{O}(T\sqrt n)\)

#include <iostream>
#include <cstdio>
#include <stdlib.h>
#include <cstring>
#include <algorithm>
#include <vector>
#define int long long
#define N 10000007
#define M 5000005
using namespace std;
const int mod = 1e8 + 9;
int prime[M],cnt,sum[N],f[N],sum2[N];
bool vis[N];
void init(int n) {
	f[1] = 1;
	for (int i = 2;i <= n;i ++) {
		if (!vis[i]) prime[++cnt] = i,f[i] = i * (1 - i) % mod;
		for (int j = 1;j <= cnt && prime[j] * i <= n;j ++) {
			vis[prime[j] * i] = 1;
			if (i % prime[j] == 0) {
				f[i * prime[j]] = f[i] * prime[j] % mod;
				break;
			}
			f[i * prime[j]] = f[prime[j]] * f[i] % mod;
		}
	}
	for (int i = 1;i <= n;i ++) sum[i] = (sum[i - 1] + f[i]) % mod;
	for (int i = 1;i <= n;i ++) sum2[i] = (sum2[i - 1] + i) % mod;
}
int n,m;
signed main(){
	init(1e7);
	int T;
	cin >> T;
	for (;T--;) {
		scanf("%lld%lld",&n,&m);
		if (m < n) swap(n,m);
		int ans = 0;
		for (int l = 1,r;l <= n;l = r + 1) {
			r = min(n / (n / l),m / (m / l));
			ans = (ans + sum2[n / l] * sum2[m / l] % mod * (sum[r] - sum[l - 1] + mod) % mod + mod) % mod;
		}
		printf("%lld\n",ans);
	}
	return 0;
}

拓展:线性筛求积性函数 \(g(x)\) 的充要条件

我们需要考虑三个部分:

  • 为质数,直接代入(已知)。
  • \(i,p\) 互质,直接用积性函数。
  • \(p\)\(i\) 的因子,那么此时设 \(i=p^km\),则 \(g(i)=g(p^k)g(m),g(ip)=g(p^{k+1})g(m)\),那么 \(g(ip)=g(i)\frac{g(p^{k+1})}{g(p^k)}\)

所以只需知道 \(\frac{g(p^{k+1})}{g(p^k)}\) 是一个什么值就可以了(或者说你可以用其他方法得到他)。

P2231 [HNOI2002] 跳蚤

之前写过题解:https://www.cnblogs.com/high-sky/p/19162197

P3172 [CQOI2015] 选数

题目概述

\(n\) 个数满足在 \([L,H]\) 中,请回答选出数的方案满足他们的 \(\gcd\)\(k\) 的答案,并对 \(10^9+7\) 取模。

数据范围:\(1\leq L,H,n,k\leq 10^9,1\leq H-L\leq 10^5\)

分析

我之前写的题解:https://www.cnblogs.com/high-sky/p/19154610

但是这里有一点需要补充,为什么要减去全部相同的情况呢?

这是因为我们把递推范围变小了,所以变换后有些 \(\gcd\) 不在这个区间里面,所以容斥不掉,因此减去即可,在最后如果有 \(1\) 就加上即可。

举个例子:假如变换后的区间为 \(L'=100,H'=200\),而我们的递推是 \([1,100]\),如果此时有 \(\gcd=150\),所以容斥不掉,而这种方案对答案影响不大,故可以单独处理。

代码

时间复杂度 \(\mathcal{O}((H-L+1)\log (H-L+1))\)

#include <iostream>
#include <cstring>
#include <stdlib.h>
#include <cstdio>
#include <algorithm>
#include <vector>
#define int long long
#define N 100005
using namespace std;
const int mod = 1e9 + 7;
int qpow(int a,int b) {
	int res = 1;
	while(b) {
		if (b & 1) res = res * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return res;
}
int n,k,l,h,f[N],ans[N];
signed main(){
	cin >> n >> k >> l >> h;
	h /= k;
	l = (l + k - 1) / k;
	if (l > h) return cout << 0,0;
	k = 1;
	for (int i = 1;i <= h - l;i ++) {
		int L = (l + i - 1) / i,R = h / i;
		if (L > R) continue;
		f[i] = qpow(R - L + 1,n) - (R - L + 1);
		f[i] %= mod;
		f[i] = (f[i] + mod) % mod;
	}
	for (int i = h - l;i;i --) {
		ans[i] = f[i];
		for (int j = i + i;j <= h - l;j += i) ans[i] = (ans[i] - ans[j] + mod) % mod;
	}
	cout << (ans[1] + (l == 1)) % mod;
	return 0;
}

P3327 [SDOI2015] 约数个数和

题意概述

\(d(x)\)\(x\) 的约数个数,给定 \(n,m\),求:

\[\sum_{i=1}^n\sum_{j=1}^md(ij) \]

数据范围:\(1\le T,n,m \le 50000\)

分析

考虑这个 \(d(ij)\) 怎么分析。

\(i=\prod_k p_k^{\alpha_k},j=\prod_k p_k^{\beta_k}\),那么:\(d(ij)=\prod_k(\alpha_k+\beta_k+1)\)

我们考虑什么时候还会变成 \(\prod_k(\alpha_k+\beta_k+1)\)(尽管这有点从答案推过程)。

我们往 \(\gcd\) 那方面想,对于 \(i,j\) 中的 \(p_k\),我可以选 \(1,2,\dots,\alpha_k\) 个,此时另外一个不选,那么就是 \((\alpha_k+\beta_k)\),那为什么 \(+1\),两个都不选呗。

这种情况的产生可以在 \(ij\) 的约数分成两个部分,一个从 \(i\) 来一个从 \(j\) 来,那么他们两个必须互质,所以:

\[\sum_{x\mid i}\sum_{y\mid j}[\gcd(x,y)=1]=d(ij) \]

转化一下不难得到:

\[\sum_{x=1}^n\sum_{y=1}^m\left\lfloor\frac n x\right\rfloor\left\lfloor\frac m y\right\rfloor[\gcd(x,y)=1] \]

经典式子变换一下:

\[\sum_{p=1}^n\mu(p)\sum_{x=1}^{\left\lfloor\frac n x\right\rfloor}\left\lfloor\frac n{xp}\right\rfloor\sum_{x=1}^{\left\lfloor\frac m x\right\rfloor}\left\lfloor\frac m{xp}\right\rfloor \]

后面的那个预处理一下就行了,然后整除分块即可。

代码

时间复杂度 \(\mathcal{O}(T\sqrt n)\)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <stdlib.h>
#include <vector>
#define int long long
#define N 50005
using namespace std;
int T;
int gcd(int a,int b) {
    return b ? gcd(b,a % b) : a;
}
int prime[N],cnt,mu[N],sums[N],summu[N];
bool vis[N];
int S(int n) {
    int res = 0;
    for (int l = 1,r;l <= n;l = r + 1) {
        r = n / (n / l);
        res += (n / l) * (r - l + 1);
    }
    return res;
}
void init(int n) {
    mu[1] = 1;
    for (int i = 2;i <= n;i ++) {
        if (!vis[i]) prime[++cnt] = i,mu[i] = -1;
        for (int j = 1;j <= cnt && prime[j] * i <= n;j ++) {
            vis[i * prime[j]] = 1;
            if (i % prime[j] == 0) break;
            mu[i * prime[j]] = -mu[i];
        }
    }
    for (int i = 1;i <= n;i ++) summu[i] = summu[i - 1] + mu[i];
    for (int i = 1;i <= n;i ++) sums[i] = S(i);
}
signed main(){
    init(5e4);
    cin >> T;
    for (int n,m;T--;) {
        scanf("%lld%lld",&n,&m);
        int ans = 0;
        if (n > m) swap(n,m);
        for (int l = 1,r;l <= n;l = r + 1) {
            r = min(n / (n / l),m / (m / l));
            int t1 = n / l,t2 = m / l;
            ans += (summu[r] - summu[l - 1]) * sums[t1] * sums[t2];
        }
        printf("%lld\n",ans);
    }
    return 0;
}
posted @ 2026-02-24 22:43  high_skyy  阅读(0)  评论(0)    收藏  举报
浏览器标题切换
浏览器标题切换end