2023“钉耙编程”中国大学生算法设计超级联赛(5)1002 GCD Magic

本文介绍了解决一道数学题的方法,利用莫比乌斯反演、二项式展开、杜教筛和数论分块技巧,将问题转化为gcd的幂次求和,通过拆分和转换求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

终于有一道赛时写出来的数学题了,赶紧写写想法

题意


∑i=1n∑j=1n[gcd(2i−1,2j−1)]K \sum_{i=1}^{n}\sum_{j=1}^n[gcd(2^i-1,2^j-1)]^K i=1nj=1n[gcd(2i1,2j1)]K

前置芝士

莫比乌斯反演、二项式展开、杜教筛、数论分块

切入

首先需要想到一个转化gcd(2i−1,2j−1)=2gcd(i,j)−1gcd(2^i-1,2^j-1)=2^{gcd(i,j)}-1gcd(2i1,2j1)=2gcd(i,j)1

通过2i−12^i-12i1的二进制是(111...11)2(111...11)_2(111...11)2感性理解

这里我的想法是一个二进制位全是1的数只能被另一个二进制位全是1的数整除,而且这另一个二进制位全是1的数的长度也一定是原来那个数的长度的约数。

所以最后会转化成二进制全是111的一个二进制位最长的数也就是长度为gcd(i,j)gcd(i,j)gcd(i,j)

严谨的数学推理这里用题解的数学推理:
(2i−1,2j−1)[j>i]=(2i−1,2j−2i)=(2i−1,2i(2j−i−1))=(2i−1,2j−i−1) (2^i-1,2^j-1)[j>i]=(2^i-1,2^j-2^i)=(2^i-1,2^i(2^{j-i}-1))=(2^i-1,2^{j-i}-1) (2i1,2j1)[j>i]=(2i1,2j2i)=(2i1,2i(2ji1))=(2i1,2ji1)
这个步骤就是辗转相除法的步骤

题解

∑i=1n∑j=1n[gcd(2i−1,2j−1)]K∑i=1n∑j=1n(2gcd(i,j)−1)K=∑d=1n∑i=1n∑j=1n(2d−1)K[gcd(i,j)=d]这里是最基础的一步,几乎所有莫比乌斯反演题都是从这里开始=∑d=1n∑i=1⌊nd⌋∑j=1⌊nd⌋(2d−1)K[gcd(i,j)=1]=∑d=1n(2d−1)K∑i=1⌊nd⌋∑j=1⌊nd⌋[gcd(i,j)=1]使f(n)=∑i=1n∑j=1n[gcd(i,j)=1]有f(n)=∑d=1nμ(d)∑i=1⌊nd⌋∑j=1⌊nd⌋1即f(n)=∑d=1nμ(d)⌊nd⌋2此时原式可以写作∑d=1n(2d−1)Kf(⌊nd⌋)展开∑d=1n(2d−1)K∑i=1⌊nd⌋μ(i)⌊nd∗i⌋2我们知道后面这个f(n)可以使用杜教筛与数论分块的方式处理出来而前面这个式子无法进行前缀和优化,所以需要一个转化将(2d−1)K拆开可以得到∑p=0KCKp2p∗d(−1)p−d转移到原式中并进行sigma交换∑p=0KCKp(−1)K−p[∑d=1n2d∗p∑i=1⌊nd⌋μ(i)⌊nd∗i⌋2]这样就可以用等差数列求和去计算2的幂次求和 \sum_{i=1}^{n}\sum_{j=1}^n[gcd(2^i-1,2^j-1)]^K\\ \sum_{i=1}^{n}\sum_{j=1}^{n}(2^{gcd(i,j)}-1)^K\\ =\sum_{d=1}^n\sum_{i=1}^{n}\sum_{j=1}^{n}(2^d-1)^K[gcd(i,j)=d]\\ 这里是最基础的一步,几乎所有莫比乌斯反演题都是从这里开始\\ =\sum_{d=1}^n\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor\frac{n}{d}\rfloor}(2^d-1)^K[gcd(i,j)=1]\\ =\sum_{d=1}^n (2^d-1)^K\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor\frac{n}{d}\rfloor}[gcd(i,j)=1]\\ 使f(n)=\sum_{i=1}^{n}\sum_{j=1}^{n}[gcd(i,j)=1]\\ 有f(n)=\sum_{d=1}^{n}\mu(d)\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor\frac{n}{d}\rfloor}1\\ 即f(n)=\sum_{d=1}^{n}\mu(d)\lfloor\frac{n}{d}\rfloor^2\\ 此时原式可以写作\\ \sum_{d=1}^n (2^d-1)^Kf(\lfloor\frac{n}{d}\rfloor)\\ 展开\\ \sum_{d=1}^n (2^d-1)^K\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\mu(i)\lfloor\frac{n}{d*i}\rfloor^2\\ 我们知道后面这个f(n)可以使用杜教筛与数论分块的方式处理出来\\ 而前面这个式子无法进行前缀和优化,所以需要一个转化\\ 将(2^d-1)^K拆开可以得到\\ \sum_{p=0}^{K}C_{K}^p 2^{p*d}(-1)^{p-d}\\ 转移到原式中并进行sigma交换\\ \sum_{p=0}^{K}C_{K}^p(-1)^{K-p}[\sum_{d=1}^n 2^{d*p}\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\mu(i)\lfloor\frac{n}{d*i}\rfloor^2]\\ 这样就可以用等差数列求和去计算2的幂次求和\\ i=1nj=1n[gcd(2i1,2j1)]Ki=1nj=1n(2gcd(i,j)1)K=d=1ni=1nj=1n(2d1)K[gcd(i,j)=d]这里是最基础的一步,几乎所有莫比乌斯反演题都是从这里开始=d=1ni=1dnj=1dn(2d1)K[gcd(i,j)=1]=d=1n(2d1)Ki=1dnj=1dn[gcd(i,j)=1]使f(n)=i=1nj=1n[gcd(i,j)=1]f(n)=d=1nμ(d)i=1dnj=1dn1f(n)=d=1nμ(d)dn2此时原式可以写作d=1n(2d1)Kf(⌊dn⌋)展开d=1n(2d1)Ki=1dnμ(i)din2我们知道后面这个f(n)可以使用杜教筛与数论分块的方式处理出来而前面这个式子无法进行前缀和优化,所以需要一个转化(2d1)K拆开可以得到p=0KCKp2pd(1)pd转移到原式中并进行sigma交换p=0KCKp(1)Kp[d=1n2dpi=1dnμ(i)din2]这样就可以用等差数列求和去计算2的幂次求和

关于数论分块的一些解析

我们知道形如∑d=1nf(d)∑i=1⌊nd⌋g(i)\sum_{d=1}^{n}f(d)\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}g(i)d=1nf(d)i=1dng(i)可以进行数论分块的基础就是可以快速求出∑i=lrf(i)\sum_{i=l}^{r}f(i)i=lrf(i)

这样在两个指针跳动的时候才可以求出整块的答案

代码

#include <bits/stdc++.h>

const int N = 1e7 + 6, mod = 998244353;
int n, k, C[20][20], INV[20];

inline int add(int a, int b) { return (a += b) >= mod ? a - mod : a; }
inline int sub(int a, int b) { return (a -= b) < 0 ? a + mod : a; }
inline int mul(int a, int b) { return 1LL * a * b % mod; }
inline void Add(int &a, int b) { a = add(a, b); }
inline void Sub(int &a, int b) { a = sub(a, b); }
inline void Mul(int &a, int b) { a = mul(a, b); }

inline int quickpow(int a, int b) {
    int re = 1;
    while(b) {
        if(b & 1) Mul(re, a);
        b >>= 1;
        Mul(a, a);
    }
    return re;
}

int prime[N], mu[N], m;
bool vis[N + 1];
inline void Prime(int x) {
	mu[1] = 1;
	for(int i = 2; i <= x; ++i) {
		if(!vis[i])	prime[++m] = i, mu[i] = mod - 1;
		for(int j = 1; j <= m && 1LL * prime[j] * i <= x; ++j) {
			vis[prime[j] * i] = 1;
			if(!(i % prime[j]))	break;
			mu[i * prime[j]] = sub(mod, mu[i]);
		}
	}
    for(int i = 1; i <= x; ++i) mu[i] = add(mu[i], mu[i - 1]);
}

//从1到n的和
std::unordered_map < int , int > ansmu, anscal1;
//杜教筛莫比乌斯函数(模板)其实也是数论分块来的
inline int ans_mu(int x) {
	if(x <= 1000000) return mu[x];
	if(ansmu[x]) return ansmu[x];
	int re = 0;
	for(int l = 2, r = 0; l <= x; l = r + 1)
		r = x / (x / l), Add(re, mul((r - l + 1), ans_mu(x / l)));
	return ansmu[x] = sub(1, re);
}

inline int calc(int n) {//计算f(n)
    if(anscal1[n]) return anscal1[n];
    int re = 0;
    for(int l = 1, r = 0; l <= n; l = r + 1) {
        r = n / (n / l);
        int cnt = n / l;
        Add(re, mul(mul(cnt, cnt), sub(ans_mu(r), ans_mu(l - 1))));
    }
    return anscal1[n] = re;
}

inline int calc2(int n, int p) {
    int re = 0;
    for(int l = 1, r = 0; l <= n; l = r + 1) {
        r = n / (n / l);
        if(p == 0) Add(re, mul(r - l + 1, calc(n / l)));//特别的
        else Add(re, mul(mul(sub(quickpow(2, 1LL * (r + 1) * p % (mod - 1)), quickpow(2, 1LL * l * p % (mod - 1))), INV[p]), calc(n / l)));
    }
    return re;
}

void solve() {
    std::cin >> n >> k;
    int ans = 0;
    for(int p = 0; p <= k; ++p) {
        int pos = C[k][p];
        if((k - p) & 1) pos = sub(mod, pos);
        Add(ans, mul(calc2(n, p), pos));
    }
    std::cout << ans << '\n';
}

signed main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    Prime(1000000);
    for(int i = 0; i <= 10; ++i) {
        C[i][0] = 1;
        for(int j = 1; j <= i; ++j) {
            C[i][j] = add(C[i - 1][j], C[i - 1][j - 1]);
        }
    }
    for(int i = 15; i >= 1; --i) INV[i] = quickpow(sub(quickpow(2, i), 1), mod - 2);
    int T = 1;
    std::cin >> T;
    while(T--) solve();
    return 0;
}

tips

这道题有点卡常所以需要在计算f(n)f(n)f(n)的时候用一个桶记录下来答案

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值