Loading

CF1158F Density of subarrays

想法非常暴力的一个 *3500 计数。感觉没有 3500,但还是挺有意思的。

首先我们先来思考下怎么计算一个序列的密度:每次找到其最小的,出现过所有数的前缀删去,删除次数就是其密度,可以自己手玩一下,还是很显然的。

然后这个东西启发我们 DP:设 \(f_{i, j}\) 表示考虑到前 \(j\) 个数,第 \(j\) 个数必选,当前删了 \(i\) 次,即密度为 \(i\) 的方案数。记 \(g_{i, j}\) 表示从区间 \([i, j]\) 中选出一个密度为 \(1\) 的子序列,且强制选 \(i, j\)\(a_j\) 只被选了一次,显然

\[g_{i, j} = 2^{cnt_{a_i}-1}\times\prod\limits_{k=1\land k\neq a_i \land k\neq a_j}^c 2^{cnt_{k}}-1 \]

\[f_{i, j} = \sum\limits_{j<k}f_{i - 1, j}g_{j + 1, k} \]

直接模拟可以在 \(\mathcal{O}(n^2)\) 的复杂度内计算 \(g\)

由于密度最大为 \(\frac{n}{c}\),于是暴力转移是 \(\mathcal{O}(\frac{n^3}{c})\) 的。在 \(c > 11\) 时这个算法可以通过。你放心,\(6\) 秒呢。(甚至据说可以通过 \(c > 1\)

然后考虑 \(c \le 11\) 怎么做,考虑直接状压,\(f_{i, j, s}\) 表示前 \(i\) 个数,密度为 \(j\),最后一次删除的前缀中哪些数已经有了,暴力转移,\(s = {1, 2, \dots, c}\)\(s \leftarrow 0, j \leftarrow j + 1\) 即可,时间复杂度 \(\mathcal{O}(\frac{n^22^c}{c}) \le \mathcal{O}(\frac{n^3}{\log n})\),同时后者也是 \(c = \log n\) 时的复杂度。

结合两个算法即可通过此题。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
// typedef __int128 i128;
typedef pair<int, int> pii;
const int N = 3e3 + 10, mod = 998244353, iv2 = 499122177;
template<typename T>
void dbg(const T &t) { cout << t << endl; }
template<typename Type, typename... Types>
void dbg(const Type& arg, const Types&... args) {
    cout << arg << ' ';
    dbg(args...);
}
int n, m, c, a[N], ans[N];
void add(int &x, int y) { x += y; if (x >= mod) x -= mod; }
void add(int &x, ll y) { x += y; if (x >= mod) x -= mod; }
void mul(int &x, int y) { x = (ll)x * y % mod; }
int qpow(int a, int b) {
    int res = 1;
    for (; b; b >>= 1, a = (ll)a * a % mod) if (b & 1) res = (ll)res * a % mod;
    return res;
}
namespace Loop1st {
int cnt[N], f[N][N], g[N][N], pw[N], inv[N];
void main() {
    pw[0] = inv[0] = 1;
    for (int i = 1; i <= n; i++) pw[i] = pw[i - 1] * 2 % mod, inv[i] = qpow(pw[i] - 1, mod - 2);
    for (int i = 1; i <= n; i++) {
        int s = -1, now = 0;
        for (int j = i; j <= n; j++) {
            if (~s) mul(s, inv[cnt[a[j]]]), g[i][j] = s;
            cnt[a[j]]++;
            if (cnt[a[j]] == 1) {
                now++;
                if (now == c) {
                    s = 1;
                    for (int k = 1; k <= c; k++) mul(s, pw[cnt[k]] - 1);
                    g[i][j] = s;
                }
            }
            if (~s) mul(s, pw[cnt[a[j]]] - 1);
        }
        for (int j = i; j <= n; j++) cnt[a[j]] = 0;
    }
    f[0][0] = 1;
    for (int i = 0; i < m; i++) {
        for (int j = 0; j <= n - c; j++) if (f[i][j]) {
            for (int k = j + c; k <= n; k++) if (g[j + 1][k]) {
                add(f[i + 1][k], (ll)f[i][j] * g[j + 1][k] % mod);
            }
        }
    }
    for (int i = 0; i <= m; i++) for (int j = 0; j <= n; j++) add(ans[i], (ll)f[i][j] * pw[n - j] % mod);
    for (int i = 0; i <= m; i++) add(ans[i], mod - ans[i + 1]);
    add(ans[0], mod - 1);
}

}
namespace Loop2nd {
int f[N][N], g[N][N], all;
void main() {
    for (int i = 1; i <= n; i++) a[i]--;
    all = 1 << c;
    f[0][0] = 1;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j <= m; j++) for (int s = 0; s < all; s++) g[j][s] = 0;
        for (int j = 0; j <= m; j++) for (int s = 0; s < all; s++) if (f[j][s]) {
            int t = s | (1 << a[i + 1]);
            if (t == all - 1) t = 0;
            add(g[j][s], f[j][s]);
            add(g[j + !t][t], f[j][s]);
        }
        for (int j = 0; j <= m; j++) for (int s = 0; s < all; s++) f[j][s] = g[j][s];
    }
    for (int i = 0; i <= m; i++) for (int s = 0; s < all; s++) add(ans[i], f[i][s]);
    add(ans[0], mod - 1);
}

}
int main() {
    // freopen("data.in", "r", stdin);
    // freopen("data.out", "w", stdout);
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin >> n >> c; m = n / c;
    for (int i = 1; i <= n; i++) cin >> a[i];
    if (c > 11) Loop1st::main();
    else Loop2nd::main();
    for (int i = 0; i <= n; i++) cout << ans[i] << " \n"[i == n];
    return 0;
}
// start coding at 15:44
// finish debugging at 16:30

posted @ 2026-01-11 16:50  循环一号  阅读(3)  评论(0)    收藏  举报