题解 守序划分问题

传送门

部分分的话 \(m\leqslant 3\) 可以手动容斥

然后正解:
尝试构造一种更容易求解的与题目限制等价的表述
与前面部分分类似地,尝试找到什么样的序列一定不合法
发现 \(m\) 个集合里如果存在一个的 max 比其他的 min 都小就肯定不合法了
然后继续拓展,发现在 \(m\) 个中如果有一个子集的 max 比其补集的 min 小就无解了
必要性反证即可
充分性的证明很难想,需要构造出一种合法的划分策略
大意是先将所有集合按照 min 递增排序
然后找到第一个满足 \(max_i > min_m\) 的位置,将 \([1, i]\) reverse 一下就是一个合法排列
于是考虑怎么对这个东西做DP
\(dp_{i, j, k}\) 为当前从小到大考虑到第 \(i\) 个数,共分了 \(j\) 个集合,其中有 \(k\) 个集合未闭合的方案数
转移的话枚举当前这个数怎么放,有

\[dp_{i, j, k}=dp_{i-1, j-1, k-1}+dp_{i-1, j-1, k}+(k+1)dp_{i-1, j, k+1}+k\times dp_{i-1, j, k} \]

于是复杂度为 \(O(nm^2)\)

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 100010
#define ll long long
//#define int long long

char buf[1<<21], *p1=buf, *p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
inline int read() {
	int ans=0, f=1; char c=getchar();
	while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
	while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
	return ans*f;
}

int n, m;
ll inv[N];
const ll mod=998244353;

namespace force{
	bool vis[N];
	ll ans;
	int len[N], mx[N], mn[N], bel[N], p[N];
	void dfs(int u) {
		if (u>n) {
			for (int i=1; i<=m; ++i) if (!len[i]) return ;
			for (int i=1; i<=m; ++i) mx[i]=0, mn[i]=INF;
			for (int i=1; i<=n; ++i) mx[bel[i]]=max(i, mx[bel[i]]), mn[bel[i]]=min(i, mn[bel[i]]);
			for (int i=1; i<=m; ++i) p[i]=i;
			do {
				mx[p[0]]=mx[p[m]]; mn[p[0]]=mn[p[m]];
				for (int i=1; i<=m; ++i) if (mx[p[i]]<mn[p[i-1]]) goto jump;
				++ans;
				// cout<<"mx: "; for (int i=0; i<=m; ++i) cout<<mx[i]<<' '; cout<<endl;
				// cout<<"mn: "; for (int i=0; i<=m; ++i) cout<<mn[i]<<' '; cout<<endl;
				// cout<<"bel: "; for (int i=1; i<=n; ++i) cout<<bel[i]<<' '; cout<<endl;
				return ;
				jump: ;
			} while (next_permutation(p+1, p+m+1));
			return ;
		}
		for (int i=1; i<=m; ++i) {
			bel[u]=i; ++len[i];
			dfs(u+1);
			--len[i];
		}
	}
	void solve() {
		dfs(1);
		printf("%lld\n", (ans*inv[m]%mod+mod)%mod);
	}
}

namespace task{
	int dp[502][502][502];
	void solve() {
		// cout<<double(sizeof(dp))/1000/1000<<endl;
		dp[0][0][0]=1;
		for (int i=1; i<=n; ++i)
			for (int j=1; j<=m; ++j)
				for (int k=(i!=n); k<=j; ++k)
					dp[i][j][k]=(1ll*dp[i-1][j-1][k]+dp[i-1][j-1][k-1]+1ll*(k+1)*dp[i-1][j][k+1]+1ll*k*dp[i-1][j][k])%mod;
		printf("%d\n", dp[n][m][0]);
	}
}

signed main()
{
	freopen("partition.in", "r", stdin);
	freopen("partition.out", "w", stdout);

	n=read(); m=read();
	inv[0]=inv[1]=1;
	for (int i=2; i<=n; ++i) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
	for (int i=2; i<=n; ++i) inv[i]=inv[i-1]*inv[i]%mod;
	// force::solve();
	task::solve();

	return 0;
}
posted @ 2022-01-13 19:03  Administrator-09  阅读(0)  评论(0编辑  收藏  举报