2024“钉耙编程”中国大学生算法设计超级联赛(3) 1005 数论
题意:
分析:
远看数论题,实则是道数据结构。
记在固定 \(gcd\) 的情况下,\(f_{i}\) 表示 \(r_{k}=i\) 的方案数,\(g_{i}\) 表示 \(l_{1}=i\) 的方案数,那么运用简单容斥,可得:
先考虑如何计算 \(f_{i}\),对于一个相同的 \(i\):
记 \(z=\gcd(a_{x},a_{x+1},\dots,a_{i})\),可以发现随着 \(x\) 变小,\(z\) 单调不升且 \(z\) 的本质不同的值不超过 \(\log V\) 个。因此可以使用二分把所有 \(z\) 相同的左端点的区间求出来,对于 \(z\) 相同的一段左端点 \(x \in [l,r]\) 而言,它在 \(dp\) 转移时必须满足上一个区间的 \(gcd\) 与这个区间的 \(gcd\) 值相等,解决方法也不难,只需要先二分离线预处理出一些四元组:
其表示左端点所属的区间为 \([l,r]\),右端点为 \(i\),\(gcd\) 为 \(z\)。根据上面的分析,这样的四元组的个数不超过 \(n \log V\) 个。我们将 \(z\) 相同的四元组按 \(i\) 排序,记 \(sum_{i}\) 表示在该 \(gcd\) 下 \(\sum_{j=1}^{i}f_{i}\),那么转移为:
转移它可以使用线段树,套路地维护 \(f_{i}\) 和 \(if_{i}\) 的前缀和,这样就能快速求出 \(s_{j}\) 的前缀和,这样就能转移了。容易发现,所求得 \(g_{i}\) 为 \(a_{i}\) 翻转后的 \(f_{n-i+1}\)。
因此对于同一 \(gcd\) 而言,得到了\(f\) 和 \(g\) 的若干点值,由于相邻两段点值对中间一段的 \(ans_{i}\) 的贡献是相同的,因此可以使用差分维护。总时间复杂度 \(O(n \log n \log V)\)。
放一份卡常失败,但是过了大样例的代码
#include <bits/stdc++.h>
#define int long long
#define N 100005
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#define mod 998244353
using namespace std;
int n, Sum;
int a[N], ans[N];
struct Tree {
int c[N * 4], tag1[N * 4], tag2[N * 4];
void init() {
memset(c, 0, sizeof(c));
memset(tag1, 0, sizeof(tag1));
memset(tag2, 0, sizeof(tag2));
}
void pushup(int u) {
c[u] = (c[u * 2] + c[u * 2 + 1]) % mod;
}
void maketag(int u, int L, int R, int tg1, int tg2) {
if(tg1 != -1) {
tag1[u] = tg1;
tag2[u] = tg2;
c[u] = (tg1 + tg2) * (R - L + 1) % mod;
}
else {
tag2[u] = (tag2[u] + tg2) % mod;
c[u] = (c[u] + tg2 * (R - L + 1) % mod) % mod;
}
}
void pushdown(int u, int L, int R) {
if(tag1[u] == -1 && tag2[u] == 0) return;
int mid = (L + R) / 2;
maketag(u * 2, L, mid, tag1[u], tag2[u]);
maketag(u * 2 + 1, mid + 1, R, tag1[u], tag2[u]);
tag1[u] = -1; tag2[u] = 0;
}
void update(int u, int L, int R, int l, int r, int x, int y) {
if(R < l || r < L) return;
if(l <= L && R <= r) {
maketag(u, L, R, x, y);
return;
}
int mid = (L + R) / 2;
pushdown(u, L, R);
update(u * 2, L, mid, l, r, x, y);
update(u * 2 + 1, mid + 1, R, l, r, x, y);
pushup(u);
}
int query(int u, int L, int R, int l, int r) {
if(R < l || r < L) return 0;
if(l <= L && R <= r) return c[u];
int mid = (L + R) / 2;
pushdown(u, L, R);
return (query(u * 2, L, mid, l, r) + query(u * 2 + 1, mid + 1, R, l, r)) % mod;
}
}t1, t2;
struct node {
int l, r, i, z;
};
vector<node>t, now;
bool cmp(node A, node B) {
if(A.z != B.z) return A.z < B.z;
else return A.i < B.i;
}
int Gcd[N][25], lg[N];
int Get_gcd(int L, int R) {
int h = lg[R - L + 1];
return __gcd(Gcd[L][h], Gcd[R - (1 << h) + 1][h]);
}
int ask(int x) {
if(x <= 0) return 0;
return ((x + 1) * t1.query(1, 1, n, x, x) % mod - t2.query(1, 1, n, x, x) + mod) % mod;
}
unordered_map<int, int>h;
int cnt;
struct ljm {
int opt, i, x; //opt=1:f opt=2:g f[i]=x
};
vector<ljm>B[N * 35];
int tiao[N];
void work(int Gcd, int opt) {
t1.update(1, 1, n, 1, n, 0, 0);
t2.update(1, 1, n, 1, n, 0, 0);
if(!h[Gcd]) {
h[Gcd] = ++cnt;
tiao[cnt] = Gcd;
}
Gcd = h[Gcd];
for(auto x : now) {
int res = ask(x.r - 1) - ask(x.l - 2) + (x.r - x.l + 1);
t1.update(1, 1, n, x.i, n, -1, res);
t2.update(1, 1, n, x.i, n, -1, res * x.i % mod);
if(opt == 1) B[Gcd].push_back((ljm){1, x.i, res});
else B[Gcd].push_back((ljm){2, n - x.i + 1, res});
if(opt == 1) Sum = (Sum + res) % mod;
}
}
void Sol(int opt) {
t1.init(); t2.init();
t.clear();
for(int i = 2; i <= n; i++) lg[i] = lg[i / 2] + 1;
for(int i = 1; i <= n; i++) Gcd[i][0] = a[i];
for(int j = 1; j <= 20; j++)
for(int i = 1; i + (1 << j) - 1 <= n; i++) Gcd[i][j] = __gcd(Gcd[i][j - 1], Gcd[i + (1 << (j - 1))][j - 1]);
//cout << "look : " << Get_gcd(1, 5) << endl;
for(int i = 1; i <= n; i++) {
int r = i, l;
while(r) {
int res = Get_gcd(r, i);
int L = 1, R = r, Get = 0, mid;
while(L <= R) {
mid = (L + R) / 2;
if(Get_gcd(mid, i) >= res) {
Get = mid;
R = mid - 1;
}
else L = mid + 1;
}
l = Get;
t.push_back((node){l, r, i, res});
//if(res == 2)
//printf("l = %lld, r = %lld, i = %lld, res = %lld \n", l, r, i, res);
r = l - 1;
}
}
sort(t.begin(), t.end(), cmp);
int L = 0;
while(L < t.size()) {
now.clear();
int R = L;
now.push_back(t[L]);
while(R + 1 < t.size() && t[R + 1].z == t[L].z) now.push_back(t[++R]);
//if(t[L].z == 2)
work(t[L].z, opt);
L = R + 1;
}
}
bool cmp2(ljm a, ljm b) {
return a.i < b.i;
}
int v[N], F[N], G[N], len;
void Get_ans(int x) {
sort(B[x].begin(), B[x].end(), cmp2);
/*printf("Gcd = %lld : \n", tiao[x]);
for(auto now : B[x]) {
if(now.opt == 1) printf("f[%lld] = %lld \n", now.i, now.x);
else printf("g[%lld] = %lld \n", now.i, now.x);
}*/
//printf("\n");
len = 0;
int lst = 0;
for(auto now : B[x]) {
if(now.i == lst) {
if(now.opt == 1) F[len] += now.x;
else G[len] += now.x;
}
else {
lst = now.i;
v[++len] = now.i;
if(now.opt == 1) F[len] += now.x;
else G[len] += now.x;
}
}
for(int i = 1; i <= len; i++) F[i] = (F[i] + F[i - 1]) % mod;
for(int i = len; i >= 1; i--) G[i] = (G[i] + G[i + 1]) % mod;
for(int i = 0; i <= len; i++) {
int L = v[i] + 1, R;
if(i == len) R = n;
else R = v[i + 1] - 1;
if(L <= R) {
int res = ((F[i] + 1) * (G[i + 1] + 1) % mod - 1 + mod) % mod;
ans[L] = (ans[L] + res) % mod;
ans[R + 1] = (ans[R + 1] - res + mod) % mod;
//printf("opt : [%lld, %lld] += %lld \n", L, R, res);
}
}
for(int i = 1; i <= len; i++) {
int res = ((F[i - 1] + 1) * (G[i + 1] + 1) % mod - 1 + mod) % mod;
int L = v[i], R = v[i];
ans[L] = (ans[L] + res) % mod;
ans[R + 1] = (ans[R + 1] - res + mod) % mod;
//printf("opt : [%lld, %lld] += %lld \n", i, i, res);
}
//printf("\n \n");
for(int i = 1; i <= len; i++) v[i] = F[i] = G[i] = 0;
}
signed main() {
//freopen("x.in", "r", stdin);
//freopen("x.out", "w", stdout);
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
//cerr << "e";
Sol(1);
reverse(a + 1, a + n + 1);
Sol(2);
for(int i = 1; i <= cnt; i++) Get_ans(i);
for(int i = 1; i <= n; i++) ans[i] = (ans[i - 1] + ans[i]) % mod;
//cerr << "Sum : " << Sum << endl;
//Sum = 24;
for(int i = 1; i <= n; i++) {
cout << (Sum - ans[i] + mod) % mod << " ";
}
return 0;
}
/*
6
3 6 12 2 4 1
*/