[ARC163D] Sum of SCC

对于所有 \(n\) 个点的竞赛图 \(G\),若其满足下面条件,则称其为好图:

  • \(G\) 中由编号较小的点指向编号较大的点的边恰好有 \(m\)

求所有好图 \(G\) 的强连通分量个数之和,对 \(998244353\) 取模。

\(1 \leq n \leq 30\)\(0 \leq m \leq \dbinom{n}{2}\)


竞赛图的性质:缩点后形成一条链。

也就是说,对于竞赛图 \(G\),其强连通分量个数等价于下面计数问题的方案数:

  • 将点集 \(V\) 划分成两个点集 \(A\)\(B\),使得 \(B\) 非空且不存在 \(B\) 连向 \(A\) 的边

我们令 \(dp_{i,j,k}\) 表示前 \(i\) 个点连 \(k\) 条小到大的边,当前 \(|A| = j\) 的方案数。

接下来考虑转移到第 \(i+1\) 个点,我们枚举其在哪个集合里。

假设其在 \(A\) 集合里,对于前面所有 \(B\) 中的点,只能连从 \(i+1\)\(B\) 的边。

对于前面 \(A\) 中的点,这个随便连,容易转移。

假设其在 \(B\) 集合里,对于前面所有 \(A\) 中的点,只能连从 \(A\)\(i+1\) 的边。

对于前面 \(B\) 中的点,这个随便连,容易转移。

结束了,复杂度 \(O(n^3 m)\)

#include<iostream>
#include<cstdio>
using namespace std;
const long long mod=998244353;
const int V=30;
long long inv(long long num){
	long long pre=mod-2,ans=1;
	while(pre){
		if(pre&1){
			ans=ans*num%mod;
		}
		num=num*num%mod;
		pre>>=1;
	}
	return ans;
}
long long fact[V+10],invfact[V+10];
void init(){
	fact[0]=1;
	invfact[0]=1;
	for(int i=1;i<=V;i++){
		fact[i]=fact[i-1]*i%mod;
		invfact[i]=invfact[i-1]*inv(i)%mod;
	}
}
inline long long C(int num1,int num2){
	if(num2<0  ||  num2>num1) return 0;
	else return fact[num1]*invfact[num2]%mod*invfact[num1-num2]%mod;
}
long long dp[40][40][510]; 
int main(){
	init();
	int n,m;
	scanf("%d %d",&n,&m);
	dp[1][0][0]=dp[1][1][0]=1;
	for(int i=1;i<n;i++){
		for(int j=0;j<=i;j++){
			for(int k=0;k<=i*(i-1)/2;k++){
				for(int l=0;l<=j;l++){
					dp[i+1][j+1][k+l]+=dp[i][j][k]*C(j,l)%mod;
					dp[i+1][j+1][k+l]%=mod;
				}
				for(int l=0;l<=i-j;l++){
					dp[i+1][j][k+l+j]+=dp[i][j][k]*C(i-j,l)%mod;
					dp[i+1][j][k+l+j]%=mod;
				}
			}
		}
	}
	long long ans=0;
	for(int i=1;i<=n;i++){
		ans+=dp[n][i][m];
		ans%=mod;
	}
	printf("%lld",ans);
	return 0;
}
posted @ 2026-01-19 16:38  Oken喵~  阅读(0)  评论(1)    收藏  举报