[题解] 约数个数和

[题解] 约数个数和 数论分块+莫比乌斯反演

题目链接
首先我们需要知道一个结论: d ( i j ) = ∑ x ∣ i ∑ y ∣ j [ gcd ⁡ ( x , y ) = = 1 ] d(ij) = \sum_{x|i}\sum_{y|j}\left[\gcd(x,y)==1\right] d(ij)=xiyj[gcd(x,y)==1]
感性理解就是对约数取了个并集。

对于这个结论,我们还有其扩展形式。
d ( i j k ) = ∑ x ∣ i ∑ y ∣ j ∑ z ∣ k [ gcd ⁡ ( x , y ) = 1 ] [ gcd ⁡ ( y , z ) = 1 ] [ gcd ⁡ ( x , z ) = 1 ] d(ijk) = \sum_{x|i}\sum_{y|j}\sum_{z|k}[\gcd(x,y) = 1][\gcd(y,z) = 1][\gcd(x,z) = 1] d(ijk)=xiyjzk[gcd(x,y)=1][gcd(y,z)=1][gcd(x,z)=1]

我们对这个式子进行变形:
d ( i j ) = ∑ x ∣ i ∑ y ∣ j [ gcd ⁡ ( x , y ) = = 1 ] = ∑ x ∣ i ∑ y ∣ j ∑ k ∣ gcd ⁡ ( x , y ) μ ( k ) = ∑ k ∣ i , k ∣ j μ ( k ) ∑ x ∣ i ∑ y ∣ j 1 \begin{aligned} d(ij) =&\sum_{x|i}\sum_{y|j}\left[\gcd(x,y)==1\right]\\ =&\sum_{x|i}\sum_{y|j}\sum_{k|\gcd(x,y)}\mu(k)\\ =&\sum_{k|i,k|j}\mu(k)\sum_{x|i}\sum_{y|j}1\\ \end{aligned} d(ij)===xiyj[gcd(x,y)==1]xiyjkgcd(x,y)μ(k)ki,kjμ(k)xiyj1
有的同学可能会有疑问,为什么这里不让 k k k 1 1 1 min ⁡ ( i , j ) \min(i,j) min(i,j)开始枚举呢?因为这里的k没有遍历 1 → min ⁡ ( i , j ) 1 \to\min(i,j) 1min(i,j)。因为总是存在 k < min ⁡ ( i , j ) k<\min(i,j) k<min(i,j),使得 k ∤ i k\nmid i ki k ∤ j k\nmid j kj
我们继续化简:
d ( i j ) = ∑ k ∣ i , k ∣ j μ ( k ) ( ∑ x ∣ i k 1 ) ( ∑ y ∣ j k 1 ) = ∑ k ∣ i , k ∣ j μ ( k ) d ( i k ) d ( j k ) \begin{aligned} d(ij) =&\sum_{k|i,k|j}\mu(k)\left(\sum_{x|\frac{i}{k}}1\right)\left(\sum_{y|\frac{j}{k}}1\right)\\ =&\sum_{k|i,k|j}\mu(k)d\left(\dfrac{i}{k}\right)d\left(\dfrac{j}{k}\right) \\ \end{aligned} d(ij)==ki,kjμ(k)xki1ykj1ki,kjμ(k)d(ki)d(kj)
我们把这个式子代入原式:

∑ i = 1 n ∑ j = 1 m d ( i j ) = ∑ i = 1 n ∑ j = 1 m ∑ k ∣ i , k ∣ j μ ( k ) d ( i k ) d ( j k ) = ∑ k = 1 min ⁡ ( n , m ) μ ( k ) ∑ k ∣ i i ≤ n d ( i k ) ∑ k ∣ j j ≤ m d ( j k ) = ∑ k = 1 min ⁡ ( n , m ) μ ( k ) ( ∑ i = 1 n k d ( i ) ) ( ∑ j = 1 m k d ( j ) ) \begin{aligned} \sum_{i=1}^{n}\sum_{j=1}^{m}d(ij) =&\sum_{i=1}^{n}\sum_{j=1}^{m}\sum_{k|i,k|j}\mu(k)d\left(\dfrac{i}{k}\right)d\left(\dfrac{j}{k}\right) \\ =&\sum_{k=1}^{\min(n,m)}\mu(k)\sum_{k|i}^{i\le n}d\left(\dfrac{i}{k}\right) \sum_{k|j}^{j\le m} d\left(\dfrac{j}{k}\right)\\ =&\sum_{k=1}^{\min(n,m)}\mu(k)\left(\sum_{i=1}^{\frac{n}{k}}d(i)\right)\left(\sum_{j=1}^{\frac{m}{k}}d(j)\right)\\ \end{aligned} i=1nj=1md(ij)===i=1nj=1mki,kjμ(k)d(ki)d(kj)k=1min(n,m)μ(k)kiind(ki)kjjmd(kj)k=1min(n,m)μ(k)i=1knd(i)j=1kmd(j)
我们设 s ( n ) = ∑ i = 1 n d ( i ) s(n) = \sum_{i=1}^{n}d(i) s(n)=i=1nd(i)
s s s d d d的前缀和,那么原式可以化为:
∑ k = 1 min ⁡ ( n , m ) μ ( k ) s ( ⌊ n k ⌋ ) s ( ⌊ m k ⌋ ) \sum_{k=1}^{\min(n,m)}\mu(k)s\left(\left \lfloor\dfrac{n}{k} \right \rfloor \right)s\left(\left \lfloor \dfrac{m}{k} \right \rfloor \right) k=1min(n,m)μ(k)s(kn)s(km)
通过交换求和符号,我们减少了某些不必要的重复枚举,降低了时间复杂度。
*注意 对于函数 s s s,我们仍然可以对其用数论分块。运用时只需要把它看成一种映射即可。
前面的 μ \mu μ可以预处理出前缀和,后面应用二维数论分块,总的时间复杂度 O ( n + T n ) O\left(n+T\sqrt{n}\right) O(n+Tn )
这里用线性筛维护 d d d的时候,我们需要记录每个正整数 i i i的最小素因子的次数 a [ i ] a[i] a[i],便于更新 d d d.
维护一个关于数的最小素因子的数组是常用手法。

#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
using namespace std;

const double eps = 1e-10;
const double pi = acos(-1.0);
const int maxn = 5e4 + 10;

int T;
int p[maxn],cnt;
bool isp[maxn];
ll mu[maxn],d[maxn];
ll a[maxn];

void getp(){
	isp[1] = 1;
	mu[1] = d[1] = 1;
	a[1] = 0;
	for(int i = 2; i < maxn; i++){
		if(!isp[i]){
			p[++cnt] = i;
			mu[i] = -1;
			d[i] = 2;
			a[i] = 1;
		}
		for(int j = 1; j <= cnt && i * p[j] < maxn; j++){
			isp[i*p[j]] = 1;
			if(i %p[j] == 0){
				mu[i*p[j]] = 0;
				a[i*p[j]] = a[i]+1;
				d[i*p[j]] = d[i]/(a[i]+1)*(a[i]+2);
				break;
			}
			else{
				mu[i*p[j]] = -mu[i];
				a[i*p[j]] = 1;
				d[i*p[j]] = 2*d[i];
			}
		}
	}
	for(int i = 1; i < maxn; i++){
		mu[i] = mu[i-1] + mu[i];
		d[i] = d[i-1] + d[i];
	}
}

void solve(){
	getp();
	scanf("%d",&T);
	while(T--){
		int n,m;
		scanf("%d%d",&n,&m);
		int t = min(n,m);
		int l = 1, r = t + 1;
		ll ans = 0;
		while(l <= t){
			r = min(n/(n/l),m/(m/l));
			ans += d[n/l]*d[m/l]*(mu[r] - mu[l-1]);
			l = r+1;
		}
		printf("%lld\n",ans);
	}
}

int main()
{
	solve();
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于洛谷上的p1036题目,我们可以使用Python来解决。下面是一个可能的解法: ```python def dfs(nums, target, selected_nums, index, k, sum): if k == 0 and sum == target: return 1 if index >= len(nums) or k <= 0 or sum > target: return 0 count = 0 for i in range(index, len(nums)): count += dfs(nums, target, selected_nums + [nums[i]], i + 1, k - 1, sum + nums[i]) return count if __name__ == "__main__": n, k = map(int, input().split()) nums = list(map(int, input().split())) target = int(input()) print(dfs(nums, target, [], 0, k, 0)) ``` 在这个解法中,我们使用了深度优先搜索(DFS)来找到满足要求的数列。通过递归的方式,我们遍历了所有可能的数字组合,并统计满足条件的个数。 首先,我们从给定的n和k分别表示数字个数和需要选取的数字个数。然后,我们输入n个数字,并将它们存储在一个列表nums中。接下来,我们输入目标值target。 在dfs函数中,我们通过迭代index来选择数字,并更新选取的数字个数k和当前总和sum。如果k等于0且sum等于target,我们就找到了一个满足条件的组合,返回1。如果index超出了列表长度或者k小于等于0或者sum大于target,说明当前组合不满足要求,返回0。 在循环中,我们不断递归调用dfs函数,将选取的数字添加到selected_nums中,并将index和k更新为下一轮递归所需的值。最终,我们返回所有满足条件的组合个数。 最后,我们在主程序中读入输入,并调用dfs函数,并输出结果。 这是一种可能的解法,但不一定是最优解。你可以根据题目要求和测试数据进行调试和优化。希望能对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值