我不会的题,那就是好题。
题意简述
假设有一个数组 a,f(k) 表示 a 所有长度为 k 的子区间的最大值的 gcd,a 是好的当且仅当在定义域内 f(k) 两两不同。求有多少个值域为 [1,m] 的整数的任意长度的 a 是好的。对 F1 Easy Version,∑m≤105;对 F2 Hard Version,m≤106。
观察
f(k) 两两不同,首先要定长区间最大值集合两两不同。从 k=2 考虑,就是要求 ∄i s.t. ai≥ai−1,ai≥ai+1。然后发现对后续的过程还要求 ai 两两不同。
手动模拟后总结一下:好的序列必须是一个严格单谷序列,并且从小到大排序后,所有后缀 gcd 两两不同。
Easy Version
既然是后缀,∑m 也很小,考虑从大到小插入数。令 fi 维护当前后缀 gcd 为 i 的序列个数,然后每次长度加一的时候因为最小值有两个位置可以插入(当前最小值的左右),所以 × 贡献过去。每次插入一个数 i,考虑 fj 向 fgcd(i,j) 贡献。直接做是 O(m2logm)。
看到 gcd 一个想法是莫反。推一推式子,假设当前考虑对 fj 的增量,且暂且不考虑贡献时 gcd(i,j)<j 的限制:
===k∑fk[gcd(k,i)=j]k∑fjk[gcd(k,ji)=1]k∑fjkd∣gcd(k,ji)∑μ(d)dj∣i∑μ(d)k∑fdjk
实时维护一下 gi=∑jfij,遍历每个 i 的因子 j,套一层遍历每个 ji 的因子 d,把 μ(d)gdj 加入贡献中。最后把限制减掉 fj 对自身的贡献,这部分的贡献 ×2。然后新建一个点的贡献就是给 fi 做一次 +1。细节上,这些都是同时做的,所以把所有的贡献暂存然后再一起加回去。
单次时间 O(∑1≤i≤n∑d∣iσ(d))。
代码:
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
| #include <bits/stdc++.h> using namespace std;
const int MXN = 100005; const int DVS = 998244353; int T, N, cnt[MXN], sum[MXN], ans, delta[MXN], mu[MXN]; vector<int> fac[MXN];
inline void add(int i, int v) { cnt[i] = (cnt[i] + v) % DVS; for (int j : fac[i]) sum[j] = (sum[j] + v) % DVS; }
int main() { mu[1] = 1; for (int i(1); i != MXN; ++i) { fac[i].push_back(i); for (int j(i + i); j < MXN; j += i) fac[j].push_back(i), mu[j] -= mu[i]; mu[i] = (DVS + mu[i]) % DVS; } cin >> T; while (T--) { cin >> N; for (int i(1); i <= N; ++i) cnt[i] = 0, sum[i] = 0; for (int i(N); i; --i) { for (int j : fac[i]) { delta[j] = (DVS - cnt[j]) % DVS; for (int k : fac[i / j]) delta[j] = (delta[j] + mu[k] * 1LL * sum[j * k]) % DVS; delta[j] = delta[j] * 2 % DVS; } delta[i] = (delta[i] + 1) % DVS; for (int j : fac[i]) add(j, delta[j]), delta[j] = 0; } ans = 0; for (int i(1); i <= N; ++i) ans = (ans + cnt[i]) % DVS; cout << ans << endl; } return 0; }
|
Hard Version
发现强制要求从小往大插,离线存一下答案。
那好吧,令 fi,j 表示当前位的后缀 gcd 填 i,当前位本身是 ij。再次考虑莫反算贡献,枚举当前 i 的每个因子 j:
==k,l∑fk,l[gcd(kl,j)=k]k,l∑fk,l[gcd(l,kj)=1]kd∣j∑μ(d)l∑fk,dl
你以为这里要两维都记然后达成 O(nlogn) 的空间复杂度?不用!只要存 gi=∑d∣iμ(d)∑lfdi,dl,每次查询 gkd 即可。复杂度不变。
这么做还是不考虑贡献时 gcd(i,j)<j 的限制,所以仍然要除去 si=∑kfi,k 对自身的贡献。那么空间上只存 g,s 两个。空间是 O(n) 的。
每次加入一个数 i,对所有 j∣i 的 fj,ji 做更新,而且每个都要 +1。而答案的差分即为 fi,1,前缀和一下即可。
时间有点紧(虽然我没卡常),可以通过存因数的时候先做 vector::reserve()
加速。
代码:
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
| #include <bits/stdc++.h> using namespace std;
const int MXN = 1000005; const int DVS = 998244353; int T, N, cnt[MXN], sum[MXN], ans[MXN], delta[MXN], mu[MXN]; vector<int> fac[MXN];
inline void add(int i, int v) { cnt[i] = (cnt[i] + v) % DVS; }
int main() { mu[1] = 1; for (int i(1); i != MXN; ++i) fac[i].reserve(10); for (int i(1); i != MXN; ++i) { fac[i].push_back(i); for (int j(i + i); j < MXN; j += i) fac[j].push_back(i), mu[j] -= mu[i]; mu[i] = (DVS + mu[i]) % DVS; } for (int i(1); i != MXN; ++i) { for (int j : fac[i]) { delta[j] = (DVS - cnt[j]) % DVS; for (int k : fac[j]) delta[j] = (delta[j] + sum[k]) % DVS; delta[j] = (delta[j] + delta[j] + 1) % DVS; } for (int j : fac[i]) { for (int k : fac[i / j]) sum[j * k] = (sum[j * k] + mu[k] * 1LL * delta[j]) % DVS; cnt[j] = (cnt[j] + delta[j]) % DVS; } ans[i] = (ans[i - 1] + cnt[i]) % DVS; } scanf("%d", &T); while (T--) scanf("%d", &N), printf("%d\n", ans[N]); return 0; }
|