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;
} 
posted @ 2026-05-29 13:29  kzssCCC  阅读(10)  评论(0)    收藏  举报