CF 2208E Counting Cute Arrays 题解

Link
写一篇简单的题解。

OP1

考虑如何从 \(A\) 数组变到 \(X\) 数组,其实可以直接使用单调栈。
操作是如果目前 \(X_i\) 在栈顶,直接将它抛出,然后把 \(i\) 压入单调栈,否则一直弹栈。
那么首先的不合法的情况是存在 \(j<i\),满足 \(X_j<X_i<j<i\),即在 \(j\) 这个点,\(X_i\) 就已经被弹出了。

OP2

现在如果 \(X_i=i-1\) 或者 \(X_i=-1\) 时怎么做,那么我们的 \(X_i\) 的弹出要么弹出若干次,要么只能弹出一次。
现在设 \(dp_{i,j}\) 表示目前考虑到 \(i\) 这个点,现在单调栈的大小为 \(j\)
那么考虑转移:

\[dp_{i,j}=dp_{i-1,j-1}+\sum\limits_{k=j-1}^i dp_{i-1,k} \]

好的,那么现在所有的前置就完了。

OP3

现在我们把一个 \(X_i \ne -1\)\(i\) 单独拎出来看。
发现 \((X_i,i)\) 中的所有点不可能成为 \(j>i\)\(X_j\) 出现(因为 \(i \leq X_j\))。
也就是说这一段是独立的。而在 OP1 中,我们发现区间要么包含,要么就不交。那么当我们从小区间(长度较小的)往大区间考虑时,小区间对于大区间没有任何影响。
现在我们假设有 \([L,R]\) 这个区间,然后区间中包含了 \([l,r]\) 这段区间,相当于我们在 \((l,r]\) 这段区间填充 \(i-1\),因为遇到这个元素后,不会删除任何东西,而是加入 \(r\) 这个点,而 \([L,R]/[l,r]\) 这段一定为 \(-1\)

那么现在我们将这个问题严格转换为了子问题,就可以递归处理了。

OP4

说一嘴实现,对于这种题,其实不用写递归,只需要搞一个指针看下一个没被考虑的点是哪一个,然后从长度小的往长度大的转移即可。
代码

#include <bits/stdc++.h>
#define FASTIO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
using ll = long long;
using pii = pair<int, int>;
const int N = 5005;
const int Md = 998244353;
int a[N], nxt[N], n;
ll ans = 0;
void calc(int l, int r) {
    vector<ll> dp, sum;
    dp.push_back(1), sum.push_back(1);
    while (true) {
        if (nxt[l] == 0) {
            if (++l > r) break;
            dp = sum;
            dp.insert(dp.begin(), 0);
        }
        else {
            if ((l = nxt[l]) > r)
                break;
            dp.insert(dp.begin(), 0);
        }
        sum = vector<ll>(dp.size());
        sum.back() = dp.back();
        for (int i = sum.size() - 2; ~i; --i)
            sum[i] = (sum[i + 1] + dp[i]) % Md;
    }
    ans = (1ll * ans * sum[0]) % Md;
}
void clear(void) {
    for (int i = 0; i <= n; ++i) nxt[i] = 0;
}
void solve(void) {
    clear();
    cin >> n;
    ans = 1;
    for (int i = 1; i <= n; ++i) cin >> a[i];
    for (int i = 1; i <= n; ++i)
        if (a[i] >= i) {
            cout << 0 << '\n';
            return;
        }
    for (int i = 1; i <= n; ++i)
        for (int j = i + 1; j <= n; ++j)
            if (a[j] != -1 && a[i] != -1 && a[i] < a[j] && a[j] < i) {
                cout << 0 << '\n';
                return;
            }
    vector<int> vec;
    for (int i = 1; i <= n; ++i)
        if (a[i] != -1) vec.push_back(i);
    sort(vec.begin(), vec.end(), [&](int x, int y) {return x - a[x] < y - a[y]; });
    for (int pos : vec) {
        calc(a[pos], pos - 1);
        nxt[a[pos]] = pos;
    }
    calc(0, n);
    cout << ans << '\n';
}
int main() {
    FASTIO;
    int t;
    cin >> t;
    while (t--) solve();
    return 0;
}
posted @ 2026-03-26 21:26  To_string  阅读(3)  评论(0)    收藏  举报