zoukankan      html  css  js  c++  java
  • 树形图求和:一道经典矩阵知识题

    题目大意

    nflsoj题目链接

    正睿 题目链接:under权限:18省选十连测

    给⼀张(n)个点(m)有向带权边的有向图。

    这张图的⼀个树形图定义为:从(m)条边中选出(n-1)条边,使得所有点都可以通过这些边走到(n)号点。

    ⼀个树形图的权值定义为这(n-1)条边的权值和。

    求出所有树形图的权值和。

    答案对(10^9+7)取模。

    数据范围:(nleq 300), (mleq 10^5), ( ext{边权}leq 10^9)

    有向图Matrix Tree

    我们在小学二年级时都学过无向图生成树计数:Matrix Tree定理。这个定理可以扩展到有向图有根树的计数。

    在无向图中,我们定义图的拉普拉斯矩阵是度数矩阵-邻接矩阵。在有向图问题中,如果是求内向树数量,则拉普拉斯矩阵是出度矩阵-邻接矩阵,如果是求外向树数量,则拉普拉斯矩阵是入度矩阵-邻接矩阵。本题显然是前一种情况。

    假设我们钦定了一个节点(u)为根(如果题目没有指定,我们就枚举这个(u)),则以(u)为根的生成树数量是拉普拉斯矩阵去掉第(u)行、第(u)列以后的矩阵的行列式. 本题中,(u=n)

    矩阵的行列式可以用带辗转相除法的高斯消元求。代码片段:

    inline int mod1(int x){return x<MOD?x:x-MOD;}
    inline int mod2(int x){return x<0?x+MOD:x;}
    struct Matrix{
    	int a[305][305];
    	Matrix(){memset(a,0,sizeof(a));}
    };
    int get_det(Matrix A,int n){
    	//行列式
    	int res=1;
    	for(int i=1;i<=n;++i){
    		for(int j=i+1;j<=n;++j){
    			while(A.a[j][i]){
    				int t=A.a[i][i]/A.a[j][i];
    				for(int k=1;k<=n;++k)A.a[i][k]=mod2(A.a[i][k]-(ll)A.a[j][k]*t%MOD);
    				swap(A.a[i],A.a[j]);res=mod2(-res);
    			}
    		}
    		res=(ll)res*A.a[i][i]%MOD;
    	}
    	return res;
    }
    

    初步转化

    本题中,我们考虑每条边对答案的贡献,是:它的权值( imes)它在多少个生成树上出现过. 设原图的生成树数量是(x_0),去掉第(i)条边以后的生成树数量是(x_i),则答案就是:

    [sum_{i=1}^{m}(x_0-x_i)w_i ]

    (x_i),可以在原图拉普拉斯矩阵去掉第(n)行、第(n)列得到的矩阵(设为矩阵(A))的基础上,让(a_{u_i,v_i}+1)(a_{u_i,u_i}-1),相当于去掉了第(i)条边,对这个新矩阵求行列式即可。复杂度(O(mn^3))

    优化

    矩阵的行列式具有如下性质:

    设矩阵(A)的第(k)行满足(forall iin[1,n],a_{k,i}=b_i+c_i). 则:

    [|A|= left|egin{matrix} a_{1,1} & a_{1,2} & cdots & a_{1,n}\ vdots & vdots & ddots & vdots\ a_{k,1} & a_{k,2} & cdots & a_{k,n}\ vdots & vdots & ddots & vdots\ a_{n,1} & a_{n,2} & cdots & a_{n,n} end{matrix} ight| = left|egin{matrix} a_{1,1} & a_{1,2} & cdots & a_{1,n}\ vdots & vdots & ddots & vdots\ b_{1} & b_{2} & cdots & b_{n}\ vdots & vdots & ddots & vdots\ a_{n,1} & a_{n,2} & cdots & a_{n,n} end{matrix} ight| + left|egin{matrix} a_{1,1} & a_{1,2} & cdots & a_{1,n}\ vdots & vdots & ddots & vdots\ c_{1} & c_{2} & cdots & c_{n}\ vdots & vdots & ddots & vdots\ a_{n,1} & a_{n,2} & cdots & a_{n,n} end{matrix} ight| ]

    (注:(|A|)表示矩阵(A)的行列式)

    于是,我们对(A)修改两个位置后求行列式,就相当于三个行列式加起来:

    [|newA|=|A|+ left|egin{matrix} a_{1,1} & a_{1,2} & cdots & a_{1,v_i}& cdots & a_{1,n}\ vdots & vdots & ddots & vdots & ddots & vdots\ a_{u_i-1,1} & a_{u_i-1,2} & cdots & a_{u_i-1,v_i}& cdots & a_{u_i-1,n}\ 0 & 0 & cdots & 1& cdots & 0\ a_{u_i+1,1} & a_{u_i+1,2} & cdots & a_{u_i+1,v_i}& cdots & a_{u_i+1,n}\ vdots & vdots & ddots & vdots & ddots & vdots\ a_{n,1} & a_{n,2} & cdots & a_{n,v_i} & cdots & a_{n,n} end{matrix} ight| + left|egin{matrix} a_{1,1} & a_{1,2} & cdots & a_{1,u_i}& cdots & a_{1,n}\ vdots & vdots & ddots & vdots & ddots & vdots\ a_{u_i-1,1} & a_{u_i-1,2} & cdots & a_{u_i-1,u_i}& cdots & a_{u_i-1,n}\ 0 & 0 & cdots & -1& cdots & 0\ a_{u_i+1,1} & a_{u_i+1,2} & cdots & a_{u_i+1,u_i}& cdots & a_{u_i+1,n}\ vdots & vdots & ddots & vdots & ddots & vdots\ a_{n,1} & a_{n,2} & cdots & a_{n,u_i} & cdots & a_{n,n} end{matrix} ight| ]

    于是我们相当于要对后面的两个特殊的矩阵求行列式。

    余子矩阵

    余子式:把一个矩阵的第(i)行、第(j)列去掉以后得到的矩阵的行列式叫(a_{i,j})的余子式,记做(M_{i,j})

    (c_{i,j}=(-1)^{i+j}M_{i,j})(a_{i,j})代数余子式

    (A)的每个位置的代数余子式构成的矩阵叫(A)余子矩阵

    (A)的行列式,除高斯消元外还有一种求法。任取(A)的一行(k),则(|A|=sum_{i=1}^{n}a_{k,i}c_{k,i})

    发现我们要求行列式的两个矩阵,除(k=u_i)这行外,其它行都与(A)相同,所以它的余子矩阵的这一行,和(A)的余子矩阵这一行是完全相同的。又因为第(k=u_i)行除了一个位置以外其他位置都是(0)。也就是说,我们只要预处理出(A)的余子矩阵(C),就可以(O(1))算出后面两个新矩阵的行列式。

    按定义预处理余子矩阵的复杂度是(O(n^5)),无法接受。但余子矩阵还有别的求法。

    定义矩阵(A)伴随矩阵(operatorname{adj}(A)=displaystyleleft(frac{A}{|A|} ight)^{-1})

    (A)的余子矩阵(C)(A)的伴随矩阵的转置矩阵,即(C=operatorname{adj}(A)^{T})

    模拟一下这个过程:先求伴随矩阵,然后求出余子矩阵,再对每条边算贡献。复杂度(O(n^3+m))

    参考代码:

    //problem:zr142
    #include <bits/stdc++.h>
    using namespace std;
    
    #define pb push_back
    #define mk make_pair
    #define lob lower_bound
    #define upb upper_bound
    #define fst first
    #define scd second
    
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int,int> pii;
    typedef pair<ll,ll> pll;
    typedef pair<int,ll> pil;
    typedef pair<ll,int> pli;
    
    namespace Fread{
    	const int MAXN=1<<20;
    	char buffer[MAXN],*S,*T;
    	inline char getchar(){
    		if(S==T){
    			T=(S=buffer)+fread(buffer,1,MAXN,stdin);
    			if(S==T) return EOF;
    		}
    		return *S++;
    	}
    }
    #ifdef ONLINE_JUDGE
    	#define getchar Fread::getchar
    #endif
    inline int read(){
    	int f=1,x=0;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    inline ll readll(){
    	ll f=1,x=0;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    
    const int MAXN=1e5+5,MOD=1e9+7;
    inline int mod1(int x){return x<MOD?x:x-MOD;}
    inline int mod2(int x){return x<0?x+MOD:x;}
    inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}
    struct E{int u,v,w;}e[MAXN];
    struct Matrix{
    	int a[305][305];
    	Matrix(){memset(a,0,sizeof(a));}
    };
    //void print(Matrix A,int n){for(int i=1;i<=n;++i){for(int j=1;j<=n;++j)cout<<A.a[i][j]<<" ";cout<<endl;}}
    int det,n,m;
    int get_det(Matrix A,int n){
    	//行列式
    	int res=1;
    	for(int i=1;i<=n;++i){
    		for(int j=i+1;j<=n;++j){
    			while(A.a[j][i]){
    				int t=A.a[i][i]/A.a[j][i];
    				for(int k=1;k<=n;++k)A.a[i][k]=mod2(A.a[i][k]-(ll)A.a[j][k]*t%MOD);
    				swap(A.a[i],A.a[j]);res=mod2(-res);
    			}
    		}
    		res=(ll)res*A.a[i][i]%MOD;
    	}
    	return res;
    }
    Matrix get_inv(Matrix A,int n){
    	//"逆"矩阵
    	int b[n+5][n*2+5];memset(b,0,sizeof(b));
    	for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)b[i][j]=A.a[i][j];
    	for(int i=1;i<=n;++i)b[i][i+n]=1;
    	for(int i=1;i<=n;++i){
    		int r=-1;
    		for(int j=i;j<=n;++j)if(b[i][j]){r=j;break;}
    		if(r==-1){puts("0");exit(0);}
    		if(r!=i)for(int j=1;j<=n*2;++j)swap(b[i][j],b[r][j]);
    		for(int j=1;j<=n;++j)if(i!=j){
    			int t=(ll)b[j][i]*pow_mod(b[i][i],MOD-2)%MOD;
    			for(int k=1;k<=n*2;++k)b[j][k]=mod2(b[j][k]-(ll)b[i][k]*t%MOD);
    		}
    		int t=pow_mod(b[i][i],MOD-2);
    		for(int j=1;j<=n*2;++j)b[i][j]=(ll)b[i][j]*t%MOD;
    	}
    	Matrix C;
    	for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)C.a[i][j]=b[i][j+n];
    	return C;
    }
    Matrix get_gay(Matrix A,int det,int n){
    	//"伴随"矩阵
    	int idt=pow_mod(det,MOD-2);
    	for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)A.a[i][j]=(ll)A.a[i][j]*idt%MOD;
    	return get_inv(A,n);
    }
    Matrix get_rev(Matrix A,int n){
    	//"转置"矩阵
    	Matrix B;
    	for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)B.a[j][i]=A.a[i][j];
    	return B;
    }
    Matrix get_FishEgg(Matrix A,int n){
    	//"鱼子"矩阵
    	return get_rev(get_gay(A,det,n),n);
    }
    int main() {
    	n=read(),m=read();
    	Matrix A;
    	for(int i=1;i<=m;++i)e[i].u=read(),e[i].v=read(),e[i].w=read(),A.a[e[i].u][e[i].v]--,A.a[e[i].u][e[i].u]++;
    	for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)A.a[i][j]=mod2(A.a[i][j]);
    	det=get_det(A,n-1);
    	Matrix B=get_FishEgg(A,n-1);
    	int ans=0;
    	for(int i=1;i<=m;++i){
    		int new_det=0;
    		new_det=B.a[e[i].u][e[i].v];
    		new_det=mod1(new_det+det);
    		new_det=mod2(new_det-B.a[e[i].u][e[i].u]);
    		ans=mod1(ans+(ll)e[i].w*mod2(det-new_det)%MOD);
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    
  • 相关阅读:
    {POJ}{3903}{Stock Exchange}{nlogn 最长上升子序列}
    {HDU}{2516}{取石子游戏}{斐波那契博弈}
    {POJ}{3925}{Minimal Ratio Tree}{最小生成树}
    {ICIP2014}{收录论文列表}
    {Reship}{KMP字符串匹配}
    kettle系列-[KettleUtil]kettle插件,类似kettle的自定义java类控件
    kettle系列-kettle管理平台部署说明
    kettle系列-我的开源kettle调度、管理平台[kettle-manager]介绍
    技术杂记-改造具有监控功能的数据库连接池阿里Druid,支持simple-jndi,kettle
    技术杂记-日期时间字符串解析识别
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/13110209.html
Copyright © 2011-2022 走看看