zoukankan      html  css  js  c++  java
  • 【洛谷7437】既见君子(状压+矩阵树定理)

    点此看题面

    • 给定一张(n)个点(m)条边的无向图,等概率选择一棵生成树,求(z)号点在(1)号点与(n)号点间路径上的概率。
    • (nle20,mle10^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 imes 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
    ",1LL*t*QP(Calc(1),X-2)%X),0;//除以总方案数
    }
    
    败得义无反顾,弱得一无是处
  • 相关阅读:
    Mvc请求管道中的19个事件
    asp.net 验证正则表达式
    Asp.net MVC进入请求管道的过程
    MVC(二)
    Aspect Oriented Programming (AOP)
    在C#中??和?分别是什么意思?
    MVC(一)
    ASP.NET 管道事件与HttpModule, HttpHandler简单理解
    Entity Framework && Lambda
    扩展类和扩展方法
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu7437.html
Copyright © 2011-2022 走看看