Atcoder Beginner Contest 254 EX 题解

前言

不知道这些网络赛啥时候变成了 \(ACM\) 式的多人组队比赛。

题意

给定 \(n\) 个点 \(m\) 条边的图,初始这个图没有边,每次选择一条边加入图,进行 \(n-1\) 次,对于所有的 \(k∈[1,n-1]\) ,求 \(k\) 次后使得无向图成为森林的概率

难度

\(AT2750\) ,对标 \(CF\) \(2900\)

算法标签

矩阵树定理,容斥原理,状压 \(DP\)

思路一

从矩阵树定理角度入手,后面我不会 \(qwq\) ,请移步官方题解!

思路二

参考了一些神仙的题解,个人作了更详尽的说明与补充,更适合和我一样的新手食用。

又是一道图计数,且 \(n\le 14\) ,图计数的问题,一般限制都比较复杂,直接处理很困难,一般的套路或者是思考方向都是状压 \(DP\) (想不出来)。

这题的大致方向和以前做过的 \(ABC213G\) \(Connectivity\) \(2\) ,有一些相似之处,那道题也是状压 \(DP\) ,后续补充一篇那道题的题解,总结图计数的 \(trick\) ,不过貌似现在的考试对图计数考得很少,但你可以当成状压好题爆切。

为方便描述,以 \(S\) 作为一个 \(n\) 位二进制状态压成的数值,\(z\) 作为所有的点都选入集合的二进制状态压缩的数值 ,第 \(i\) 位为 \(1\) 表示选择了第 \(i\) 个点。

\(g_{S,i}\) 表示 \(S\) 状态下,构成边数为 \(i\) 的方案数,这个状态是典型的求什么设什么,然后推递推式子的时候发现难以直接递推,于是设出一个辅助递推数组 \(f\) ,设 \(f_S\) 表示 \(S\) 状态时,可以构成一棵树的方案数。

值得注意的是,这个 \(f,g\) 数组都是指的树或者图的结构形态的方案数,并没有考虑加边的顺序。

对于每个 \(k\) 的答案,就是加入 \(k\) 条边,整体考虑,那么所有的点必定要选入集合,那么总方案数是 \(m^k\) ,形态数是 \(g_{z,k}\) ,但由上文,没有考虑加边顺序,所以乘上加边顺序方案数,也就是 \(k!\) ,答案的式子显然,请自行补全。

考虑怎么求 \(f\) ,考虑把当前的这个选取的点集划分为两个点集,也就是枚举当前状态的子集 \(T\) ,那如果记 \(S-T\) 表示从 \(S\) 里把 \(T\) 抽出去剩下的集合,则这一次产生的贡献为 \(f_T\) \(×\) \(f_{S-T}\) \(×\) \(edge(T,S-T)\) ,最后这个表示点集 \(T\)\(S-T\) 之间的边数,从中任意选一条边就可以合并这两个分裂的部分。

但是这样你发现会算重是吧,\(T\)\(S-T\) 都会作为当前集合被枚举,同时会计算补集,\(T\) 的补集是 \(S-T\)\(S-T\) 的补集是 \(T\) ,这样构成的树是一样的!此为算重第一处,只需要答案除以 \(2\) ,至于第二处 ,考虑断掉树上的一条边,剩下的两个子集组成了这棵树,那么对于这棵固定的树,断掉任意一条边得到的两个子集,都会组成这棵同样的树,设边数是 \(E\) ,算重了 \(E\) 次,答案除以 \(E\)

怎么求 \(edge(x,y)\) 呢?开头输入边的时候,对于每条边都枚举 \(2^n\) 个状态,判断是否包含,用一个数组 \(tot_j\) 记录在这个状态 \(j\) 的数量即可,容斥一下,\(edge(x,y)=tot_{x|y}-tot_x-tot_y\)

\(g\) 的方法,枚举当前是一棵怎样的树合并了进来,那不还是枚举子集吗,设当前枚举到 \(S\) 的一个子集 \(T\) ,那贡献就是 \(f_T\) \(×\) \(g_{S-T,i-|T|+1}\) ,怎么理解?当前最后一次加入的是状态为 \(T\) 的树,那之前的状态就是 \(S-T\) ,状态是 \(T\) 的树的边数是 \(|T|-1\) ,就是这样,因为这里考虑的不是分裂的子集,所以一定不会算重。

代码使用的变量名和以上是基本一致的,方便阅读。

#include<cstdio>
#include<vector>
#define N 1<<15
#define p 998244353
#define ll long long
using namespace std;
ll n,m,a[N],A,B,k;
ll fac[N],tot[N],f[N],siz[N],g[N][505];
vector<ll>v[N];
ll lowbit(ll x){return x&(-x);}
ll val(ll x,ll y){return tot[x|y]-tot[x]-tot[y];}
ll ksm(ll a,ll b)
{
	ll s=1;
	while(b)
	{
		if(b&1) s=(s*a)%p;
		a=(a*a)%p;
		b=(b>>1);
	}
	return s;
}
int main()
{
	scanf("%lld%lld",&n,&m);fac[0]=f[0]=1;k=(1<<n)-1;
	for(ll i=1;i<=k;i++) siz[i]=siz[i>>1]+(i&1);
	for(ll i=1;i<=n;i++) fac[i]=fac[i-1]*i%p;
	for(ll i=1;i<=m;i++)
	{
		scanf("%lld%lld",&A,&B);
		for(ll j=0;j<=(1ll<<14)-1;j++)
		{
			if((!(j&(1<<(A-1))))||(!(j&(1<<(B-1))))) continue; 
            tot[j]++;
		}
	}
	for(ll i=1;i<=k;i++) v[siz[i]].push_back(i);
	for(ll i=0;i<v[1].size();i++) f[v[1][i]]=1; 
	for(ll i=2;i<=n;i++)
	{
		for(ll j=0;j<v[i].size();j++)
		{
			ll tmp=0,S=v[i][j];
			for(ll x=(S-1)&S;x;x=(x-1)&S)
			{
				ll y=(S^x);
				tmp=(tmp+f[x]*f[y]%p*val(x,y)%p)%p;
			}
			f[S]=tmp*ksm(2*(i-1),p-2)%p;
		}
	}
	for(ll i=0;i<=n-1;i++)
	{
		for(ll S=0;S<=k;S++)
		{
			if(i==siz[S]-1) g[S][i]=f[S];
			ll bit=lowbit(S);
			for(ll x=S;x;x=(x-1)&S)
			{
				if(!(x&bit)) continue;
				ll y=(S^x);
				if(i-siz[x]+1>=0) g[S][i]=(g[S][i]+f[x]*g[y][i-siz[x]+1]%p)%p;
			}
		}
	}
	for(ll i=1;i<=n-1;i++) printf("%lld\n",g[k][i]*fac[i]%p*ksm(ksm(m,i),p-2)%p);
	return 0;
} ````
posted @ 2022-06-12 17:06  Constant1227  阅读(77)  评论(0)    收藏  举报