题解:CF2206I Growth Factor
模拟赛题。
但是 ds 用了 5min 做出。
但是我用了 3h 做出。可能我这个做法有点魔怔,感觉很硬。
题意:给出一个序列 \(a\),求满足以下条件的序列 \(b\) 个数:
-
\(b_i\le a_i\)
-
\(b_i\) 是 \(b_{i+1}\) 的因数。\(n,q\le 2\times 10^5\),模拟赛为 \(n,q\le 10^6\)。
做法:
先让 \(a\) 后缀取 \(\min\) 变成单调不降。
朴素想法记录位置和值域,复杂度 \(O(n^2\log n)\),这也太拉了。
记录位置显然没什么前途,我们考虑记录值域去转移。从小往大做发现信息稍微有点少,后面的可能性有点多,从大往小直接就知道了质因子的情况,感觉会好做一点。
根据经典结论,我们考虑 \(b\) 只有 \(\log\) 个段,差分一下变成乘若干次,我们可以快速算出来,从 \(1\) 开始做 \(k\) 次乘 \(>1\) 的数变成 \(x\) 的方案数,复杂度 \(O(V\log ^2V)\)。记 \(x\) 做 \(k\) 次系数为 \(coef_{x,k}\)
预处理之后继续考虑,考虑记序列末尾为 \(x\),我们发现,如果我们直接枚举因子去转移,我们还是需要一个位置信息,但是如果满足 \(a\) 全部相等就非常好,这样我们这一整段就可以直接转移出去,只需要枚举出段的时候的数是多少,就可以根据上面的预处理系数直接转移。
所以我们考虑从后往前对于 \(a\) 相同的一段一起做,每次只转移值域在 \((a_{i-1},a_i]\) 这一块的数。这样就可以保证在一个后缀中这些数都没有什么限制。
以上大概都是思路,我们来明确一下转移状态:\(dp_i\) 代表以任意开始,从大到小填,到目前值为 \(i\) 的答案。
我们限定,这样转移的时候一定每次直接转移到 \([1,a_{i-1}]\) 中,中间的过程因为我们目前 \(\le a_i\),所以中间我们怎么填都是对的,用预处理系数可以算出来,大概描述一条路径长成这样:

那么相邻的转移系数就是好算的,直接枚举乘了多少次和因子即可,还要再额外乘上一个组合数代表我需要从一段数中选出来若干个数乘。我们发现这里很坏,还是需要距离信息,但是非常好的是,我们发现红色的箭头这一段在别的转移过来的时候,可以直接确定的长这段的长度,那么我们提前钦定我们需要乘多少次,dp 状态多加一维,就可以转移了,细节可以见代码。复杂度是 \(O(V\log^2V)\) 的,常数很小。
一些常数优化:我们可以处理出每个数的质因子个数,那么其最多用这么多次就可以生成,可以优化很多枚举乘法次数的上界。
代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 5, mod = 998244353;
int n, a[maxn], dp[maxn][21], l[maxn], C[maxn][21], inv[maxn], mx, coef[maxn][21];
vector<int> fac[maxn];
int add(int x, int y) {
x += y;
return (x >= mod ? x - mod : x);
}
int lg[maxn], prime[maxn], tot, vis[maxn];
void prepare(int N) {
for (int i = 2; i <= N; i++) {
if(!vis[i])
prime[++tot] = i, lg[i] = 1;
for (int j = 1; j <= tot && prime[j] * i <= N; j++) {
vis[i * prime[j]] = 1;
lg[i * prime[j]] = lg[i] + 1;
if(i % prime[j] == 0)
break;
}
}
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = n - 1; i >= 1; i--)
a[i] = min(a[i], a[i + 1]); mx = a[n];
prepare(mx);
for (int i = mx; i >= 1; i--) {
l[i] = (i == mx ? n : l[i + 1]);
while(a[l[i] - 1] >= i)
l[i]--;
}
inv[0] = inv[1] = 1;
for (int i = 2; i <= 20; i++)
inv[i] = (mod - mod / i) * inv[mod % i] % mod;
C[0][0] = 1;
for (int i = 1; i <= n; i++) {
C[i][0] = 1;
for (int j = 1; j <= min(20, i); j++)
C[i][j] = add(C[i - 1][j], C[i - 1][j - 1]);
}
for (int i = 1; i <= mx; i++)
for (int j = 2 * i; j <= mx; j += i)
fac[j].push_back(i);
coef[1][0] = 1;
for (int i = 1; i <= mx; i++) {
for (int j = 0; j < fac[i].size(); j++)
for (int k = 1; k <= lg[i]; k++)
coef[i][k] = add(coef[i][k], coef[fac[i][j]][k - 1]);
}
int len = 0;
for (int i = n; i >= 1; i--)
len += (a[i] == a[n]);
for (int i = 1; i <= mx; i++) {
int x = a[l[i]];
for (int k = 0; k <= lg[i]; k++)
dp[i][k] = add(dp[i][k], C[n - l[i] + (i > a[1])][k]);
}
int ans = 0;
for (int i = mx; i >= 1; i--) {
int id = a[l[i] - 1];
for (int j = 0; j < fac[i].size() && fac[i][j] <= id; j++) {
int x = fac[i][j], y = i / x;
int s = 0;
for (int k = 0; k <= lg[y]; k++)
s = add(s, 1ll * dp[i][k] * coef[y][k] % mod);
if(!s)
continue;
int lt = l[i] - l[x] - 1 + (x > a[1]);
for (int k = 0; k <= lg[x]; k++)
dp[x][k] = add(dp[x][k], 1ll * s * C[lt][k] % mod);
}
if(id == 0) {
for (int j = 0; j < fac[i].size(); j++)
for (int k = 0; k <= lg[i]; k++)
ans = add(ans, 1ll * dp[i][k] * coef[i / fac[i][j]][k] % mod);
ans = add(ans, dp[i][0]);
}
}
cout << ans << endl;
return 0;
}

浙公网安备 33010602011771号