【THUWC2019模拟2019.1.18】Counting

Description

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

Solution

  • 一道题意使我懵逼的题目
  • 既然我们要求的是强联通分量序列不同的个数,我们不妨设f[i]表示连了i条边的个数。然后我们枚举又合并了多少个强联通分量。
  • 显然i条边是不足以表示这个状态的。
  • 考虑与强联通分量有关的几个量:点数,链数…
  • 注意到有一种可以构造最少强联通分量的贪心的连法,从一个强联通分量中分出一条链,然后再用一条边把这个链变成强联通分量。
  • 一团强联通分量伸出一条链,没有别的边,这可以使我们用最少的边构造出最多种可能的连法(雾),从而使得计数不会缺漏。
  • 根据以上的思路,设状态f[i][j][k]表示i条边,j+k个点在当前的联通块内,伸出一条长度为k的链,不同强连通分量序列的个数(也就是阶段性的答案)。
  • 如果j+k<n,新的一条边要么从链连到一个不在联通块内的点f[i][j][k]->f[i+1][j][k+1]
    ,要么将l个链上的点合并带强联通分量中f[i][j][k]->f[i+1][j+l][k+l].
  • 否则如果k>0我们还可以把这些点合并。
  • 但要是k=0的话,我们就不能再贪心地连边了,接下来连的边就是对答案无关紧要的了。换句话说,就是不管连什么都不会改变强联通分量的个数。f[i][j][k]->f[i+1][j][k]
  • 在转移过程中,应该保证边数在一个范围内。即i<=j*(j-1)+k*(k-1)/2+j*k。保证定义的状态的性质。
  • 这题对于我来说还是十分有思维难度的。。。。。。
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define maxn 105
#define ll long long 
#define mo 998244353
using namespace std;

int n,i,j,k,l,p,q;
ll f[2][maxn][maxn],ans;

int main(){
	freopen("counting.in","r",stdin);
	freopen("counting.out","w",stdout);
	scanf("%d",&n);
	f[0][1][0]=1;
	for(i=0;i<(n-1)*n;i++){
		if (i==6){
			printf("");
		}
		p=i&1,q=p^1;
		memset(f[q],0,sizeof(f[q]));
		ans=0;
		for(j=0;j<=n;j++) for(k=0;k<=n-j;k++) if (f[p][j][k]){
			for(l=1;l<=k;l++) 
				(f[q][j+l][k-l]+=f[p][j][k])%=mo;
			if (j+k<n) (f[q][j][k+1]+=f[p][j][k])%=mo;
			else if (i+1<=j*(j-1)+k*(k-1)/2+j*k)
				(f[q][j][k]+=f[p][j][k])%=mo;
		}
		for(j=0;j<=n;j++) for(k=0;k<=n-j;k++) 
			(ans+=f[q][j][k])%=mo;
		printf("%lld ",ans);
	}
}

posted @ 2019-01-18 22:55  Deep_Thinking  阅读(81)  评论(0编辑  收藏  举报