2026 杭电多校第六场的两道数论题

文艺复兴

\((a, b) = \gcd (a, b)\)

Problem C. Phi Master

给定一个序列 \(a_1, a_2, \cdots, a_n(1\le n\le 2\times 10 ^ 6)\),求对于所有满足 \(1\le x \le 10 ^ 7\) 的整数 \(x\),定义:

\[F_x = \max\limits_{1\le i\le n}\varphi(xa_i) \]

对于所有 \(1\le x\le 10 ^ 7\),求出 \(F_x\)

首先有个结论:

\[\varphi(ij) = {\varphi(i)\varphi(j)(i, j)\over \varphi((i, j))} \]

那么,对于每个 \(x\),要求的就变成了:

\[\varphi(x)\max\limits_{1\le i\le n} {\varphi(a_i)(x, a_i)\over \varphi((x, a_i))} \]

\(\max\) 里面的部分。

现在发现一个问题,就是对于 \(x\)\(a_i\) 呈现倍数关系的 \((x, a_i)\),我们能很简单的得到,但是若不是这样,就很难办。

我们发现式子里面 \({(x, a_i)\over \varphi((x, a_i))}\) 之和 \(x\)\(a_i\)\(\gcd\) 有关(设 \(d = (x, a_i)\))。所以我们只要求出对于每个 \(d\) 求出了最大的 \(\varphi(a_i)\) 应该就能解决问题。

具体来说,我们设 \(t_x = [x\in a_i]\varphi(x)\),然后对 \(t\) 做一个 Dirichlet 后缀和,然后令 \(t_x\leftarrow t_x \times {x\over \varphi(x)}\),再做一个 Dirichlet 前缀和,再令 \(t_x\leftarrow t_x \times\varphi (x)\),这个时候 \(t\) 就是 \(F\)

但是这里有一个问题,就是这样会不会出现点对 \(x, a_i\),设 \(d=(x, a_i)\),存在 \(k\mid d\),使得:

\[{\varphi(a_i) d\over \varphi(d)} < {\varphi(a_i) k\over \varphi(k)} \]

这样就会让答案偏大。

但是我们知道 \(\varphi(n) = n \prod \frac{p_i - 1}{p_i}\)\(p\)\(n\) 的所有质数因子,那么:

\[{n\over \varphi(n)} = \prod{p_i\over {p_i - 1}} \]

由于 \(k\) 里面的质数 \(d\) 里面肯定有,那么显然有:

\[{d\over \varphi(d)} \ge {k\over\varphi(k)} \]

所以我们就 \(\mathcal O(n\log\log n)\) 的解决了问题。

#include <bits/stdc++.h>
#define forn(i,s,t) for(int i=(s);i<=(t);++i)
#define form(i,s,t) for(int i=(s);i>=(t);--i)
#define rep(i,s,t) for(int i=(s);i<(t);++i)
using namespace std;
typedef long long ll;
const int N = 1e7 + 2;
int phi[N], p[N], cnt, vis[N];
void table(int n = 1e7) {
    phi[1] = 1;
    forn (i, 2, n) {
        if (!vis[i]) phi[i] = i - 1, p[++cnt] = i;
        for (int j = 1; j <= cnt && 1ll * p[j] * i <= n; ++j) {
            vis[p[j] * i] = 1;
            if (i % p[j] == 0) {
                phi[i * p[j]] = phi[i] * p[j];
                break ;
            }
            phi[i * p[j]] = phi[i] * (p[j] - 1);
        }
    }
}
void solve() {
    int n;
    cin >> n;
    int m = 1e7;
    vector<ll> a(m + 1, 0);
    rep (i, 0, n) {
        int x;
        cin >> x;
        a[x] = phi[x];
    }
    forn (i, 1, cnt) for (int j = m / p[i]; j; --j)
        a[j] = max(a[j], a[j * p[i]]);
    forn (i, 1, m) a[i] = a[i] * i / phi[i];
    forn (i, 1, cnt) for (int j = 1; 1ll * j * p[i] <= m; ++j)
        a[j * p[i]] = max(a[j * p[i]], a[j]);
    forn (i, 1, m) a[i] = phi[i] * a[i];
    vector<ll> A(1000, 0);
    int B = 1000;
    forn (x, 1, m) A[x % B] ^= a[x] * ((x + B - 1) / B);
    rep (i, 0, B) cout << A[i] << '\n';
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int zeg;
    cin >> zeg; table();
    while (zeg -- ) solve();
    return 0;
}

Problem F. GCD Master

给定 \(n\),求 \(\sum\limits_{i = 1} ^ n\sum\limits_{j = i} ^ n\sum\limits_{k = i} ^ j \gcd(i, k)\gcd(j, k){j\choose k}\)

多测,\(\sum n\le 5\times 10 ^ 5\),时限 10000 ms

发现题目关于 \(k\) 的东西最多,\(j\) 的第二多,交换求和次序:

\[\sum\limits_{k = 1} ^ n\sum\limits_{j = k} ^ n {j\choose k}(j, k)\sum\limits_{i = 1} ^ k (i, k) \]

\(\mathcal F(k) = \sum\limits_{i = 1} ^ k (i, k)\),欧拉反演:\(n = \sum\limits_{d\mid n}\varphi (d)\),得:

\[\mathcal F(k) = \sum\limits_{i = 1} ^ k \sum\limits_{d\mid (i, k)} \varphi(d) = \sum\limits_{d\mid k} \varphi(d) \frac{k}{d} \]

这个可以 \(\mathcal O(n)\) 或者 \(\mathcal O(n\log n)\) 求。

原式变为:

\[\sum\limits_{k = 1} ^ n \mathcal F(k) \sum\limits_{j = k} ^ n {j\choose k} (j, k) \]

\(\mathcal G(k) = \sum\limits_{j = k} ^ n {j\choose k} (j, k)\),继续欧拉反演:

\[\mathcal G(k) = \sum\limits_{j = k} ^ n {j\choose k} \sum\limits_{d\mid (j, k)} \varphi(d) = \sum\limits_{d\mid k} \varphi(d) \sum\limits_{j = 1} ^ {\left\lfloor{ n\over d}\right\rfloor} {jd\choose k} \]

展开组合数(设负数的阶乘为 \(0\)):

\[\mathcal G(k) = {1\over k!} \sum\limits_{d\mid k}\varphi(d) \sum\limits_{j = 1} ^ {\left\lfloor{ n\over d}\right\rfloor}{(jd)!\over (jd - \frac{k}{d}d)!} \]

发现对于每个 \(d\),设 \(a_i = (id)!\)\(b_i = \frac{1}{(id)!}\),那么 \(\sum\limits_{j = 1} ^ {\left\lfloor{ n\over d}\right\rfloor}{(jd)!\over (jd - \frac{k}{d}d)!} = \sum\limits_{j = 1} ^ {\left\lfloor{ n\over d}\right\rfloor} a_j b_{j - {k\over d}}\)

这是一个差卷积,对于每个 \(d\) 进行 ntt 即可,时间复杂度 \(\mathcal O\left(\sum\limits_{i}{n\over i}\log {n\over i} \right) \le \mathcal O(n\log ^ 2n)\)

#include <bits/stdc++.h>
#define forn(i,s,t) for(int i=(s);i<=(t);++i)
#define form(i,s,t) for(int i=(s);i>=(t);--i)
#define rep(i,s,t) for(int i=(s);i<(t);++i)
using namespace std;
typedef long long ll;
const int N = 5e5 + 5;
const int Mod = 998244353;
ll q_pow(ll p, ll k = Mod - 2) {
    ll r = 1;
    for (; k; p = p * p % Mod, k >>= 1)
        (k & 1) && (r = r * p % Mod);
    return r;
}
ll fac[N], ifac[N], phi[N], F[N];
int pr[N], cnt, vis[N];
#define Poly vector<ll>
const int M = 2e6 + 4;
int tr[M];
#define Poly vector<ll>
inline void table(int n) {
	static int lstn = 0;
	if (n == lstn) return ;
	rep (i, 0, n) tr[i] = (tr[i >> 1] >> 1) | ((i & 1) ? (n >> 1) : 0);
	lstn = n;
}
const ll Gmod = 3, iGmod = q_pow(3);
inline void NTT(Poly& f, int n, int fl) {
	rep (i, 0, n) if (i < tr[i]) swap(f[i], f[tr[i]]);
	for (int p = 2; p <= n; p <<= 1) {
		int len = p >> 1;
		ll id = q_pow(fl == 1 ? Gmod : iGmod, (Mod - 1) / p);
		for (int s = 0; s < n; s += p) {
			ll buf = 1;
			rep (t, s, s + len) {
				ll tmp = buf * f[t + len] % Mod;
				f[t + len] = (f[t] - tmp + Mod) % Mod;
				f[t] = (f[t] + tmp) % Mod;
				buf = buf * id % Mod;
			}
		}
	}
	if (fl == -1) {
		ll invn = q_pow(n);
		rep (i ,0, n) f[i] = f[i] * invn % Mod;
	}
}
inline Poly operator * (Poly A, Poly B) {
	int n = 1;
	while (n < (int)A.size() + (int)B.size())
		n <<= 1;
	A.resize(n), B.resize(n), table(n);
	NTT(A, n, 1), NTT(B, n, 1);
	rep (i, 0, n) A[i] = A[i] * B[i] % Mod;
	NTT(A, n, -1);
	return A;
}
Poly H[N]; int st[N];
void Table(int n = 5e5) {
    fac[0] = 1;
    forn (i, 1, n) fac[i] = fac[i - 1] * i % Mod;
    ifac[n] = q_pow(fac[n]);
    form (i, n - 1, 0) ifac[i] = ifac[i + 1] * (i + 1) % Mod;
    phi[1] = 1;
    forn (i, 2, n) {
        if (!vis[i]) phi[i] = i - 1, pr[++cnt] = i;
        for (int j = 1; j <= cnt && 1ll * pr[j] * i <= n; ++j) {
            vis[pr[j] * i] = 1;
            if (i % pr[j] == 0) {
                phi[i * pr[j]] = phi[i] * pr[j];
                break ;
            }
            phi[i * pr[j]] = phi[i] * (pr[j] - 1);
        }
    }
    forn (i, 1, n) for (int j = i; j <= n; j += i)
        F[j] += phi[i] * j / i, F[j] %= Mod;
}
inline void GetH(int n) {
    forn (d, 1, n) {
        int m = n / d;
        Poly Fc(m + 1, 0), iFc(m + 1, 0);
        forn (i, 0, m) {
            Fc[i] = fac[i * d];
            iFc[m - i] = ifac[i * d];
        }
        H[d] = Fc * iFc, st[d] = m;
    }
}
ll C(int n, int r) {
    if (n < 0 || r < 0 || n < r) return 0;
    return fac[n] * ifac[r] % Mod * ifac[n - r] % Mod;
}
inline ll bruteforce(int n) {
    ll ans = 0;
    forn (i, 1, n) forn (j, i, n) forn (k, i, j)
        ans += C(j, k) * __gcd(i, k) % Mod * __gcd(j, k) % Mod, ans %= Mod;
    return ans;
}
inline ll BruteG(int n, int k) {
    ll ans = 0;
    forn (j, k, n) ans += C(j, k) * __gcd(j, k) % Mod, ans %= Mod;
    return ans;
}
inline ll bruteforce1(int n) {
    ll ans = 0;
    forn (k, 1, n) ans += F[k] * BruteG(n, k) % Mod, ans %= Mod;
    return ans;
}
inline ll Quick(int n) {
    GetH(n);
    vector<ll> dp(n + 1, 0);
    forn (d, 1, n) for (int k = d; k <= n; k += d) {
        if ((int)H[d].size() - 1 >= st[d] + k / d)
            dp[k] += phi[d] * H[d][st[d] + k / d] % Mod,
            dp[k] %= Mod;
    }
    ll ans = 0;
    forn (k, 1, n) ans += F[k] * dp[k] % Mod * ifac[k] % Mod, ans %= Mod;
    return ans;
}
void solve() {
    int n;
    cin >> n;
    // cout << bruteforce(n) << '\n';
    // cout << bruteforce1(n) << '\n';
    cout << Quick(n) << '\n';
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int zeg;
    cin >> zeg; Table();
    while (zeg -- ) solve();
    return 0;
}
posted @ 2026-08-07 10:14  AxDea  阅读(9)  评论(0)    收藏  举报