Prufer序列 生成树定理

Description

在图论中,树的定义是连通且无环的无向图。对于一棵有 nn 个节点且节点从 11 到 nn 编号的树,它的 Prufer 序列是一个唯一的长为 n2n−2 的标号序列。 Prufer 序列的构造方法:每次删除树中标号最小的叶子节点(即度为 11 的节点),将该点的邻居加到当前 Prufer 序列的末尾,直到只剩两个节点为止。

例子:

给定一个 nn 个顶点(从 11 到 nn 标号), mm 条边的无向图 GGGG 中无重边或自环)。随机选择 GG 的一棵生成树,计算他的 Prufer 序列的和 SS重复元素只算一次。 请计算随机变量 SS 的期望。注意,GG 的生成树或某棵生成树的 Prufer 序列都可能不存在,这种情况下,我们认为随机变量 SS 的值为 00

为了避免精度问题, 算出实际的期望值乘以图 GG 的不同生成树的数目以后的值即可。 这个值可能很大,请输出它对 109+7109+7 取模以后的值。

Input

每个输入文件包含多组测试数据。输入文件的第一行是测试数据组数 TT (T10T≤10)。 对于每组测试数据,第一行是两个整数 n,mn,m (3n100,0m(n1)n23≤n≤100,0≤m≤(n−1)n2 ),分别是图的点数和边数;接下来 mm 行,每行包含两个整数 u,vu,v1u,vn1≤u,v≤nuvu≠v),表示图中的一条边。

Output

输出 TT 行,每行是对应的答案。

Sample Input

1
3 3
1 2
2 3
1 3

Sample Output

6

题目大意:求所有生成树的prufer序列和(prufer中有重复序列的只算一次!!!)

题解:

这样的话,对于每一颗生成树,我们可以把所有的点全都加进去,然后再减去叶子结点的和。

我们不可能找到所有的生成树然后一个一个的计算,因此我们用矩阵树定理来做。

我们先计算图所有的点的和,并且乘以生成树的数量,把他们放在sum里。然后再把所有的叶子结点减去,就好了

如果一个叶子节点出现在一颗子树里,那么把这个点去掉,仍然可一得到图的该生成树,而如果这个点是内部节点就不行了。

注意:如果这个叶子节点的度不为1,那么要用这个叶子节点的度数乘以生成树的数量,才是这个叶子节点对应生成树的个数。

sum -= 去掉该节点生成树的数量*该节点的度*该节点的值。

最后得到的sum就是答案

代码:

#include<iostream>。
#include<cmath>
#include <cstring>
using namespace std;
#define zero(x)((x>0? x:-x)<1e-15)
#define int long long
int const MAXN = 105;
const int mod = 1e9 + 7;
int a[MAXN][MAXN];
int b[MAXN][MAXN];
int g[103][103];
int d[105];
int n, m;
int det(int a[MAXN][MAXN], int n){ 
    int s=0;
    for(int i=0; i<n; i++){ 
        int r=i; 
        for(; r<n; r++)    // error-prone 
            if(a[r][i]) break; 
        if(r==n+1) return 0; 
  
        if(r!=i){ 
            s^=1; 
            for(int j=i; j<n; j++) 
                swap(a[i][j], a[r][j]); 
        } 
        for(int j=i+1; j<n; j++){ 
            int x=i, y=j; 
            for(; a[y][i]; ){ 
                // print(a, n); 
                int t=a[y][i]/a[x][i]; 
                if(t){ 
                    for(int k=i; k<n; k++){ 

                        a[y][k] -= t*a[x][k]%mod; 
                        a[y][k] %= mod; 
                    } 
                    if(a[y][i]==0) break; 
                } 
                swap(x, y); 
            } 
            if(x!=i){ 
                for(int k=i; k<n; ++k) 
                    swap(a[x][k], a[y][k]); 
                s ^= 1; 
            } 
        } 
    } 
    int res=1; 
    for(int i=0; i<n; i++) 
        res*=a[i][i], res%=mod; 
    if(s)
    { 
        res=-res; 
    }
    if(res < 0) res+=mod; 
    return res; 
} 



void prep(int n,int x)
{
	for(int i = 0;i < n;i++)
	{
		for(int j = 0;j < n;j++)
		{
			a[i][j] = (i == j)?d[i]-g[i][x]:-g[i][j];
		}
	}
	if(x + 1)
	{
		for(int i = 0;i < n;i++) a[x][i] = a[i][x] = 0;
		a[x][x] = 1;
	}
}

main() 
{
   	int cas;
  	scanf("%lld", &cas);
   	while (cas--) {
    	memset(g,0,sizeof(g));
    	memset(d,0,sizeof(d));
    	memset(a,0,sizeof(a));
    	memset(b,0,sizeof(b));
        scanf("%lld%lld", &n,&m);
        for(int i = 0;i < m;i++)
        {
        	int u,v;
        	scanf("%lld%lld",&u,&v);
        	u--,v--;
        	d[u]++;
        	d[v]++;
        	g[u][v] = g[v][u] = 1;
		}
		prep(n,-1);
		int sum = det(a,n-1)*((1+n)*n/2) % mod;
		for(int i = 0;i < n-1;i++)
		{
			prep(n,i);
			sum = (sum - (i+1)*d[i] % mod * det(a,n-1) % mod + mod)%mod;
			//cout<<':'<<sum<<endl;
		}
		prep(n,n-1);
		sum = (sum - n*d[n-1] % mod * det(a,n-2) % mod + mod)%mod;

		cout<<sum<<endl;
		
    }
    return 0;
}






  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值