2025CCPC郑州邀请赛暨河南省赛 B. 随机栈 II 题解
链接:Dashboard - 2025 National Invitational of CCPC (Zhengzhou), 2025 CCPC Henan Provincial Collegiate Programming Contest - Codeforces
思路:
1.设置 dp 状态:
dp[i][j][k] : 总共已经取了 i 个数, j 是取的最后一个数,最后连续取了 k 个 j,保持递增的概率
只有在处理取出操作时才会更新 dp 状态
2.其他存储变量:
cnt[j] : 取之前j的总个数
cur : 取了几次了
total : 还剩几个数可以选
3.最朴素的转移方程
for (1 <= i <= n)for (1 <= j <= n + 1)if (k == 1) 新选一个数接在j后面for (1 <= jj <= j - 1)for (1 <= kk <= cnt[jj])dp[i][j][1] += dp[i - 1][jj][kk] * (cnt[j] / total)if (k != 1) 接着选j接在j后面for (2 <= k <= min(cnt[j], cur))dp[i][j][k] = dp[i - 1][j][k - 1] * ((cnt[j] - (k - 1)) / total)
很明显这样空间和时间复杂度都会爆掉,因此需要优化
空间使用滚动数组优化维度 i 即可
时间复杂度中,根据 dp 状态定义:j 是取的最后一个数,最后连续取了 k 个 j,总状态数不超过 n 个状态,因此 j 维度循环和 k 维度循环的总和时间复杂度为 ,只需要优化掉 jj 层循环和 kk 层循环。发现只有 dp[i - 1][jj][kk] 是在循环中用到的,考虑使用变量 pre 记录 dp[i - 1][jj][kk] 的总和来优化这两层循环降低时间复杂度。
注意到 jj 是小于等于 j - 1 的,所以我们在计算完第 j 层 k == 1 的情况后,将 i - 1 层的第 j 层的值加到 pre 中,在计算第 j + 1 层的时候,就能直接用 pre 来代替 dp[i - 1][jj][kk] 的总和了。
4.优化时间复杂度后的转移方程
for (1 <= i <= n)for (1 <= j <= n + 1)if (k == 1) 新选一个数接在j后面dp[i][j][1] += pre * (cnt[j] / total)pre += dp[i - 1][j][1]; if (k != 1) 接着选j接在j后面for (2 <= k <= min(cnt[j], cur))dp[i][j][k] = dp[i - 1][j][k - 1] * ((cnt[j] - (k - 1)) / total)pre += dp[i - 1][j][1];
5.完整代码
#include<bits/stdc++.h>
using namespace std;
#define qio ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
typedef long long ll;
typedef double db;
#define int llconst int N = 5e3 + 10;
const int mod = 998244353;int a[N], cnt[N];
int invx[N];
int dp[2][N][N];int qpow(int x, int y){int res = 1;while(y){if(y & 1) res = res * x % mod;x = x * x % mod;y >>= 1;}return res;
}
int inv(int x){return qpow(x, mod - 2);
}void solve() {int n; cin >> n;for(int i = 1; i <= n; i++){cin >> a[i];// ++防止k - 1越界a[i]++;} // 初始化清零for(int i = 0; i <= n + 5; i++){cnt[i] = 0;for(int j = 0; j <= n + 5; j++){dp[0][i][j] = dp[1][i][j] = 0;}}int total = 0, cur = 0;// y为滚动数组状态int y = 0;// 初始化设为1 -> 第一次的pre为1dp[y ^ 1][0][0] = 1;// 记录出现过的数set<int> have;for(int i = 1; i <= n; i++){if(a[i] == 0){cur++;int pre = dp[y ^ 1][0][0];for(int j = 1; j <= n + 1; j++){// k == 1dp[y][j][1] += pre * (cnt[j] * invx[total] % mod) % mod;dp[y][j][1] %= mod;// 处理到j层时,累加y ^ 1层的,j层的所有值pre += dp[y ^ 1][j][1];pre %= mod; // k != 1for(int k = 2; k <= min(cnt[j], cur); k++){dp[y][j][k] += dp[y ^ 1][j][k - 1] * ((cnt[j] - (k - 1)) * invx[total] % mod) % mod;dp[y][j][k] %= mod;pre += dp[y ^ 1][j][k];pre %= mod; } }// 取出一个数后,总数--total--;// 重置另一个状态for(int j = 0; j <= n + 1; j++){for(int k = 0; k <= cnt[j]; k++){dp[y ^ 1][j][k] = 0;}}// 改变状态y ^= 1;}else{have.insert(a[i]);total++;cnt[a[i]]++;}}int ans = 0;for(auto j : have){for(int k = 1; k <= cnt[j]; k++){ans += dp[y ^ 1][j][k];ans %= mod;}}cout << ans << '\n';
}signed main() {qioint T = 1;// 记录inv值for(int i = 1; i <= N - 1; i++) invx[i] = inv(i);cin >> T;while (T--) solve();return 0;
}