本文最后更新于:2024年11月19日 下午
纪念第一次场切 ARC E。
对于每个 1≤m≤n,我们把求和式给写下来:
S∈/∅,S∈{1,2,…,n}∑i=1∑∣S∣−1gcd(ASi,ASi+1)
直接枚举是 O(2n×n) 的,考虑计算每对 (i,j) 对答案的贡献,那么转换为:
i=1∑nj=i+1∑ngcd(i,j)f(i,j)
f(i,j) 即为 2n−j×2i−1,因为 1∼i−1 随便选,j+1∼n 同理,因此这个式子成立。
接下来就开始大力推式子了:
i=1∑nj=i+1∑ngcd(i,j)f(i,j)=i=1∑nj=i+1∑ngcd(i,j)2i−12n−j=i=1∑nj=i+1∑n2i−12n−jd∣Ai,d∣Aj∑φ(d)=d=1∑Dφ(d)1≤i<j≤n,d∣ai,d∣aj∑2i−12n−j=d=1∑Dφ(d)i=1∑n[d∣ai]2i−1j=i+1∑n[d∣aj]2n−j=d=1∑Dφ(d)i=1∑n[d∣ai]2n+i−1j=i+1∑n[d∣aj]2−j
做一波前缀和即可。
时间复杂度 O(n+VlogV+∑dai)。
参考代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| #include <bits/stdc++.h> #define int long long using namespace std; constexpr int mx = 1e5 + 6, P = 998244353; int n, a[500006]; int phi[mx], vis[mx], pr[mx], tot; int pw[500006], pw_inv[500006]; int s[500006], sd[mx]; int pre[500006]; int32_t main() { ios::sync_with_stdio(0); cin.tie(0), cout.tie(0); cin >> n; for (int i = 1; i <= n; ++i) cin >> a[i]; phi[1] = 1; for (int i = 2; i < mx; ++i) { if (!vis[i]) { pr[++tot] = i; phi[i] = i - 1; } for (int j = 1; j <= tot && i * pr[j] < mx; ++j) { vis[i * pr[j]] = 1; if (i % pr[j] == 0) { phi[i * pr[j]] = phi[i] * pr[j]; break; } phi[i * pr[j]] = phi[i] * (pr[j] - 1); } } vector<vector<int>> divs(mx); for (int i = 1; i < mx; ++i) { for (int j = i; j < mx; j += i) { divs[j].emplace_back(i); } } int inv2 = (P + 1) / 2; pw[0] = pw_inv[0] = 1; for (int i = 1; i <= n; ++i) { pw[i] = pw[i - 1] * 2 % P; pw_inv[i] = pw_inv[i - 1] * inv2 % P; } for (int i = 1; i <= n; ++i) { for (auto v : divs[a[i]]) { s[i] += phi[v] * sd[v] % P; s[i] %= P; } for (auto v : divs[a[i]]) { sd[v] = (sd[v] + pw[i - 1]) % P; } } for (int i = 1; i <= n; ++i) { pre[i] = (pre[i - 1] + s[i] * pw_inv[i] % P) % P; } for (int i = 1; i <= n; ++i) { cout << pre[i] * pw[i] % P << '\n'; } }
|