把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷7437】既见君子(状压+矩阵树定理)

点此看题面

  • 给定一张\(n\)个点\(m\)条边的无向图,等概率选择一棵生成树,求\(z\)号点在\(1\)号点与\(n\)号点间路径上的概率。
  • \(n\le20,m\le10^5\)

轻松口胡出了\(O(2^nn^3)\)的做法,只是一开始没想到这玩意能过又思索半天无果。

下定决心写了一发竟有\(95\)分(最后一个点刚好\(T\)掉),反手加个读优就过了,精准卡在\(1.50s\)的时间线上。。。

状压\(DP\)求路径方案

考虑这就等价于图中要有一条从\(1\)\(z\)的路径和一条从\(n\)\(z\)的路径,二者无交点。

因此我们先设\(f_{i,j}\)表示从\(1\)号点出发,经过点集为\(i\),当前走到了\(j\)的方案数。

同理设\(g_{i,j}\)表示从\(n\)号点出发,但是这里的\(i\)就需要建立在先前基础上,把从\(1\)号点出发经过的点集也一同包含进去(因为我们只需知道到达过的点集,至于是从谁出发到达的不需要知道)。

然后设一个\(s_i\)表示这两条路径包含的点集共为\(i\)的方案数作为最终要用的数组。

转移很简单,枚举\(j\)的一个相邻点\(k\)乘上它们之间的边数转移。

特殊地,如果\(k=z\)\(f_{i,j}\)将转移到\(g_{i,n}\)\(g_{i,j}\)将转移到\(s_i\)

矩阵树定理求生成树个数

路径上的点集为\(i\),说明这个点集已经确定连通。因此我们把\(i\)中的所有点视作一个点,那么只要用矩阵树定理就可以求出缩点后的连边方案数了。

假设这个方案数为\(Calc(i)\),那么总方案数就应该是\(Calc(1)\)(此时没有缩点,于是任挑一个点视作要缩点的点集)

因此最终答案就是\(\frac{\sum s_i\times Calc(i)}{Calc(1)}\)

代码:\(O(2^nn^3)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 20
#define X 998244353
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
using namespace std;
int n,m,z,f[1<<N][N+5],g[1<<N][N+5],s[1<<N],w[N+5][N+5];
I int QP(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
namespace FastIO
{
	#define FS 100000
	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
	char oc,FI[FS],*FA=FI,*FB=FI;
	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
}using namespace FastIO;
int a[N+5][N+5];I int Gauss(CI n)//高斯消元求行列式
{
	RI i,j,k,t,p=1;for(i=1;i<=n;p=1LL*p*a[i][i]%X,++i)
	{
		if(!a[i][i]) {for(p=X-p,j=i+1;j<n&&!a[j][i];++j);for(k=i;k<=n;++k) swap(a[i][k],a[j][k]);}
		for(j=i+1;j<=n;++j) for(t=1LL*(X-a[j][i])*QP(a[i][i],X-2)%X,k=i;k<=n;++k) a[j][k]=(1LL*t*a[i][k]+a[j][k])%X;
	}return p;
}
int id[N+5];I int Calc(CI S)//矩阵树定理,把S中的点视作一个点
{
	RI i,j,t=1;for(i=1;i<=n;++i) id[i]=(S>>i-1&1)?1:++t;for(i=1;i<=t;++i) for(j=1;j<=t;++j) a[i][j]=0;//编号;清空
	for(i=1;i<=n;++i) for(j=1;j<=n;++j) a[id[i]][id[i]]+=w[i][j],a[id[i]][id[j]]-=w[i][j];//度数矩阵-邻接矩阵
	for(i=1;i<=t;++i) for(j=1;j<=t;++j) a[i][j]<0&&(a[i][j]+=X);return Gauss(t-1);
}
int main()
{
	RI i,j,k,x,y;for(read(n,m,z),i=1;i<=m;++i) read(x,y),++w[x][y],++w[y][x];
	RI l=1<<n-2;for(f[0][1]=1,i=0;i^l;++i) for(j=1;j<=n;++j) if(f[i][j])//DP从1到z的路径
		for(k=2;k^n;++k) x=1LL*f[i][j]*w[j][k]%X,k^z?!(i>>k-2&1)&&Inc(f[i|1<<k-2][k],x):Inc(g[i][n],x);//枚举点转移,z特殊转移
	for(i=0;i^l;++i) for(j=1;j<=n;++j) if(g[i][j])//DP在从1到z路径确定的基础上,从n到z的路径
		for(k=2;k^n;++k) x=1LL*g[i][j]*w[j][k]%X,k^z?!(i>>k-2&1)&&Inc(g[i|1<<k-2][k],x):Inc(s[i],x);//枚举点转移,z特殊转移
	RI t=0;for(i=0;i^l;++i) t=(1LL*s[i]*Calc(i<<1|1|1<<z-1|1<<n-1)+t)%X;//路径连边方案数×缩点后连边方案数
	return printf("%d\n",1LL*t*QP(Calc(1),X-2)%X),0;//除以总方案数
}
posted @ 2021-05-05 18:41  TheLostWeak  阅读(96)  评论(0编辑  收藏  举报