2026“钉耙编程”中国大学生算法设计春季联赛(7)1003思路分享(dp,组合计数)

题意概述

给定正整数 \(n\)(天数),考虑所有满足以下条件的区间集合 \(S\)

  1. 每个区间 \([l, r] \in S\) 满足 \(1 \leq l \leq r \leq n\)

  2. \(S\) 中任意两个不同的区间互不包含。

  3. \(S\) 可以是空集。

对于集合 \(S\),定义其所需最少乐队数 \(\chi(S)\) 为:将 \(S\) 中的区间分配给乐队,使得同一乐队负责的区间互不相交,所需的最少乐队数量。

对于每个 \(k = 1, 2, \dots, n\),计算满足

\[\chi(S) \leq k \]

的集合 \(S\) 的总数。

答案对 \(998244353\) 取模。

多测,\(1 \leq n \leq 500\)\(\sum n \leq 2000\)

思路

定义每个位置的覆盖次数为 \(t_i\),容易发现,所需最少乐队数为 \(\max t_i\)

对于合法的 \(t\) 序列,满足 \(|t_i-t_{i+1}| \le 1\)

可以分以下情况讨论:

  1. $t_i = t_{i+1} - 1 $,此时 \(i+1\) 位置为某个左端点。

  2. $t_i = t_{i+1} + 1 $,此时 \(i\) 位置为某个右端点。

  3. \(t_i = t_{i+1}\) ,此时要么 \(i\)\(i+1\) 都被平铺;要么 \(i\) 是某个右端点,同时 \(i+1\) 是某个左端点。

情况 \(1,2\) 都具有强制性,在情况 \(3\) 有两种不同的选法,记满足 \(t_i = t_{i+1} \gt 0\) 的个数 \(k\),因此贡献为 \(2^{k}\)

对于所有可能的 \(t\) 序列,考虑 \(dp\) 维护。

定义 \(dp_{k,i,j}\) 为当前 \(t_i\) 上界为 \(k\),第 \(i\) 个位置,覆盖数量 \(t_i = j\) 时,可能的 \(S\) 数量。

因为区间左右端点需要匹配,\(j\) 需要满足可以收敛到 \(0\),考虑多开一个位置。

按上述三种情况转移,不产生新贡献的直接加,产生新贡献的乘 \(2\) 再加,最后 \(dp_{k,n+1,0}\) 为答案。

时间复杂度 \(\mathcal{O}(n^3)\)

代码

//author:kzssCCC

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

const int MOD = 998244353;

void solve(){
	int n;
	cin >> n;

	for (int k=1;k<=n;k++){
		vector<vector<ll>> dp(n+2,vector<ll>(k+1));
		dp[0][0] = 1;

		for (int i=1;i<=n+1;i++){
			for (int j=0;j<=k;j++){
				if (dp[i-1][j]==0) continue;

				if (j+1<=k){
					dp[i][j+1] = (dp[i][j+1]+dp[i-1][j])%MOD;
				}

				if (j-1>=0){
					dp[i][j-1] = (dp[i][j-1]+dp[i-1][j])%MOD;
				}

				if (j>0){
					dp[i][j] = (dp[i][j]+dp[i-1][j]*2%MOD)%MOD;
				}
				else{
					dp[i][j] = (dp[i][j]+dp[i-1][j])%MOD;
				}
			}
		}		

		cout << dp[n+1][0] << ' ';
	}
	
	cout << '\n';
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	
	int t;
	cin >> t;
	while (t--) solve();

	return 0;
}
posted @ 2026-05-10 12:17  kzssCCC  阅读(23)  评论(0)    收藏  举报