CF2119D 思路分享(dp,组合计数)
https://codeforces.com/problemset/problem/2119/D
题意概述
给定一个数组 \(a\),满足 \(0 \le a_i \le i\),定义 \(f(a)\) 为:
-
初始在数轴上 \([1,n]\) 各有一个标记。
-
依次进行 \(n\) 次操作。对于第 \(i\) 次操作,如果 \(a_i \ne 0\),从 \([a_i,i]\) 范围内移除一个未被移除的标记;否则不进行操作。
-
\(f(a)\) 为移除标记的方案数。
给定 \(n,m\),求所有合法的 \(a\) 序列的 \(f(a)\) 之和,模 \(m\)。
\(1\le n \le 5000\)。
思路
考虑对于固定序列 \(a\) 的 \(f(a)\)。对于每个 \(a_i \ne 0\) 的位置,令选择移除标记的位置为 \(p_i\),这样构成序列 \(p\)。
发现难以直接求出 \(f(a)\),转而考虑对于固定的序列 \(p\),如何求对应序列 \(a\) 的数量。
因为 \(1\le a_i \le p_i\),所以对应序列 \(a\) 的数量为 \(\prod{p_i}\)。
得到这个性质之后,考虑 \(dp\),每个移除标记的位置 \(p_i\) 都对应一个执行该操作的位置 \(i\),相当于括号匹配,维护未匹配的左括号数量即可。
定义 \(dp[i][j]\) 为枚举到第 \(i\) 个位置,有 \(j\) 个未匹配的左括号。对于前一个状态 \(dp[i-1][j]\),转移有如下情况:
-
匹配一个左括号,有 \(j\) 种选择,\(dp[i][j-1] += dp[i-1][j] \times j\)。
-
放置一个左括号,乘上贡献 \(i\),\(dp[i][j+1] += dp[i-1][j] \times i\)。
-
放置一个左括号同时匹配一个左括号,\(dp[i][j] += dp[i-1][j]\times i \times (j+1)\)
-
什么也不做,\(dp[i][j] += dp[i-1][j]\)。
最终答案为 \(dp[n][0]\)。
时间复杂度 \(\mathcal{O}(n^2)\)。
代码
//author:kzssCCC
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void solve(){
int n,MOD;
cin >> n >> MOD;
vector<ll> dp(n+1);
dp[0] = 1;
for (int i=1;i<=n;i++){
auto ndp = dp;
for (int j=0;j<=n;j++){
if (dp[j]==0) continue;
if (j-1>=0){
ndp[j-1] = (ndp[j-1]+dp[j]*j%MOD)%MOD;
}
if (j+1<=n){
ndp[j+1] = (ndp[j+1]+dp[j]*i%MOD)%MOD;
}
ndp[j] = (ndp[j]+dp[j]*i%MOD*(j+1)%MOD)%MOD;
}
dp = ndp;
}
cout << dp[0] << '\n';
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
cin >> t;
while (t--) solve();
return 0;
}

浙公网安备 33010602011771号