6009. 【THUWC2019模拟2019.1.18】Counting

15 篇文章 0 订阅
2 篇文章 0 订阅

题意:

羽月最近发现,她发动能力的过程是这样的:
构建一个 V V V 个点的有向图 G G G,初始为没有任何边,接下来羽月在脑中构建出一个长度为 E E E 的边的序列,序列中元素两两不同,然后羽月将这些边依次加入图中,每次加入之后计算当前图的强连通分量个数并记下来,最后得到一个长度为 E E E 的序列,这个序列就是能力的效果。
注意到,可能存在边的序列不同而能力效果相同的情况,所以羽月想请你帮她计算能发动的不同能力个数,答案对 998244353 998244353 998244353 取模。你需要对于 1 ≤ E ≤ V ∗ ( V − 1 ) 1\leq E \leq V*(V-1) 1EV(V1)的所有 E E E 计算答案。

数据范围:

对于 10 10 10%的数据, 1 ≤ V ≤ 5 1 \leq V \leq 5 1V5
对于 30 30 30%的数据, 1 ≤ V ≤ 20 1 \leq V \leq 20 1V20
对于 60 60 60%的数据, 1 ≤ V ≤ 50 1 \leq V \leq 50 1V50
对于 100 100 100%的数据, 1 ≤ V ≤ 100 1 \leq V \leq 100 1V100

Analysis:

我们发现有用的仅仅是那个每一步的强连通分量序列。
一开始有 n n n个强连通分量,我们考虑用简单的情况构造。
1 1 1伸出去到 i i i一条链,然后剩余是单独的点也是强连通分量,在那条链上不断缩强连通分量,这样我们可以涵盖所有强连通分量变化的情况。
考虑每一步怎么变,要不是新加一个点到链上,或者往前连一条没用的边(保持连通分量不变),要不就是一个点往前连一条环边然后和前面的点缩到一起。注意连没用的边一定是在把所有的点加到链上之后,因为他们产生的效果相同,这里在转移的时候要特判。
那么我们考虑设 f i , j , k f_{i,j,k} fi,j,k表示当前用了 i i i条边,在链上前面的连通分量里的点是 1 1 1~ j j j,链上一共有 k k k个点。
那么转移就如上即可,注意判掉不合法情况,就是连没用边和环边时,边可能会超出能连数目。我们考虑链上有 n n n个点,有 i i i个点在环内。那么 n n n个点可以往后连, i i i个点可以往前连,那么一共可以连 n ∗ ( n − 1 ) + i ∗ ( i − 1 ) 2 \frac{n*(n-1)+i*(i-1)}{2} 2n(n1)+i(i1)条边。
我们发现涉及到的状态一共也只有 V 4 V^4 V4个,因此暴力转移即可。
复杂度 O ( V 4 ) O(V^4) O(V4)

Code:

# include<cstdio>
# include<cstring>
# include<algorithm>
using namespace std;
const int N = 1e2 + 5;
const int mo = 998244353;
int f[N * (N - 1)][N][N];
int n;
inline int inc(int x,int y)
{ return x + y >= mo ? x + y - mo : x + y; }
inline int dec(int x,int y)
{ return x - y < 0 ? x - y + mo : x - y; }
int main()
{
	freopen("counting.in","r",stdin);
	freopen("counting.out","w",stdout);
	scanf("%d",&n);
	f[0][1][1] = 1;
	for (int i = 0 ; i < n * (n - 1) ; ++i)
		for (int j = 1 ; j <= min(n,i + 1) ; ++j)
			for (int k = j ; k <= min(n,i + 1) ; ++k)
			if (f[i][j][k])
			{
				if (i <= k + j - 2 && k < n) f[i + 1][j][k + 1] = inc(f[i + 1][j][k + 1],f[i][j][k]);
				else if ((k * (k - 1) + j * (j - 1)) / 2 >= i + 1) f[i + 1][j][k] = inc(f[i + 1][j][k],f[i][j][k]);
				for (int l = 1 ; l <= k - j ; ++l)
				if ((k * (k - 1) + (j + l) * (j + l - 1)) / 2 >= i + 1) f[i + 1][j + l][k] = inc(f[i + 1][j + l][k],f[i][j][k]);
			}
	for (int i = 1 ; i <= n * (n - 1) ; ++i)
	{
		int ans = 0;
		for (int j = 1 ; j <= min(n,i + 1) ; ++j)
			for (int k = j ; k <= min(n,i + 1) ; ++k) ans = inc(ans,f[i][j][k]);
		printf("%d ",ans);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值