hdu 5498 Tree 动态规划+快速矩阵幂+生成树计数+高斯消元

Tree

Time Limit: 16000/8000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 33    Accepted Submission(s): 13

Problem Description soda has an undirected graph with 

n vertices and 

m edges. He is playing a game with 

q rounds. In each round, he randomly chooses an edge from the graph. After 

qrounds, he removes the duplicated edges. If the remaining edges form a tree, soda wins the game.

soda wants to know the number of different ways to choose edge in each round that will make him win the game.

As the answer will be very large, soda gives you another integer 

p. You should print the answer modulo 

p.

Note that two ways are considered different if at least in one round soda chooses different edges.  

Input There are multiple test cases. The first line of input contains an integer 

T, indicating the number of test cases. For each test case:

The first line contains four integers 

n,m,p,q 

(2n100,1mn(n1)2,1p,q109). Each of the following 

m lines contains a pair of integers 

x and 

y, that show that an edge exists between vertices 

x and 

y 

(1 \le x, y \le n, x \ne y). For each pair of vertices there will be at most one edge between them, no edge connects a vertex to itself.

 

Output For each test case, print the answer modulo 

p in a single line.  

Sample Input

2 6 6 23 10 6 3 6 4 5 1 2 5 1 4 5 4 6 5 23 5 5 6 4 6 3 1 5 1 1 2  

Sample Output

16 5  

Source
BestCoder Round #58 (div.1)  

这是一道非常综合的题目:

给一些边,有q次操作,每次选出一条边,q次以后把重复的边删除。问有多少种取法能够使得最后的结果是一棵树。

方法:

1. 对于一棵树,相当于q次操作选中了n-1种边,因为对图的任意一个树的子图,选择的方案数是一样的

定义dp[i][j]表示第i次操作选中了j种边的方案数 那么  dp[i+1][j] = dp[i][j]*j+dp[i-1][j-1]*(n-1-j )

转移要么从选了j-1条边转移,或者从选了j条边转移。构造 n×n的矩阵,把系数加进去,然后用快速幂求解即可

2. 一个图能够构成树的情况数

转自:http://www.cnblogs.com/kuangbin/archive/2013/07/27/3219707.html

Matrix-Tree定理(Kirchhoff矩阵-树定理)。Matrix-Tree定理是解决生成树计数问题最有力的武器之一。它首先于1847年被Kirchhoff证明。在介绍定理之前,我们首先明确几个概念:
1、G的度数矩阵D[G]是一个n*n的矩阵,并且满足:当i≠j时,dij=0;当i=j时,dij等于vi的度数。
2、G的邻接矩阵A[G]也是一个n*n的矩阵, 并且满足:如果vi、vj之间有边直接相连,则aij=1,否则为0。

我们定义G的Kirchhoff矩阵(也称为拉普拉斯算子)C[G]为C[G]=D[G]-A[G],则Matrix-Tree定理可以描述为:G的所有不同的生成树的个数等于其Kirchhoff矩阵C[G]任何一个n-1阶主子式的行列式的绝对值。所谓n-1阶主子式,就是对于r(1≤r≤n),将C[G]的第r行、第r列同时去掉后得到的新矩阵,用Cr[G]表示。

那么问题就变成求矩阵行列式的问题了。之前用用寻找列主元的方法对模数是质数的题做过一次

改题的博客:http://blog.csdn.net/firenet1/article/details/38065929

这题要对合数取模,比较困难。

其实消元要考虑的问题就是,处理第A[i][i]位置的时候,如何把A[j][i] (j > i)的位置且不为0的行的第i列变成0.

考虑a = A[i][i], b = A[j][i]

没次假设abs(a) > abs(b) (其他情况的话可以交换)

然后令a = a – b*a/b

如此操作几次以后a,b中就会出现一个0,结束即可。

然后判断A[i][i]是不是0,是的话和第j行交换即可。

==============复杂度分析,A[i][i]不为0的时候,每次操作新的A[i][i] <= 旧的A[i][i],所以均摊下来的复杂度是比较低的。

这种消元方法的代码也很好写,比较简单—————–(—666 不是我想到的,我是请教别人,然后他想出来的)

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<math.h>
using namespace std;
#define ll long long
#define maxn 101
int p;
struct Node{
    int mat[maxn][maxn];
    void init(){
        memset(mat,0,sizeof(mat));
    }
    void one(int n){
        init();
        for(int i = 0;i < n; i++)
            mat[i][i] = 1;
    }
    int Det(int n,  int mod1){ //计算行列式对mod1取模
        int ans = 1;
        int *p1,*p2,mul;

        for(int i = 0;i < n; i++){
            for(int j = i+1;j < n; j++){
                while(mat[i][i] != 0 && mat[j][i] != 0){
                    if(abs(mat[i][i]) > abs(mat[j][i]))
                        p1=mat[i],p2=mat[j],mul=mat[i][i]/mat[j][i];
                    else
                        p1=mat[j],p2=mat[i],mul=mat[j][i]/mat[i][i];
                    for(int l = i;l < n; l++)
                        p1[l] = (p1[l]-(ll)p2[l]*mul)%mod1;
                }
                if(mat[i][i] == 0){
                    ans = -ans;
                    for(int l = i;l < n;l++)
                        swap(mat[i][l],mat[j][l]);
                }
            }
            ans = (ll)ans*mat[i][i] % mod1;
        }
        return (ans+mod1)%mod1;
    }
};
int matrix[maxn][maxn];
void multi(Node& a,Node& b,int n){ //矩阵乘法----加判断以减小计算次数
    for(int i = 0;i < n; i++)
        for(int j = 0;j < n; j++)
            matrix[i][j] = 0;
    for(int i = 0;i < n; i++){
        for(int j = 0;j < n; j++){
            if(a.mat[i][j] == 0) continue;
            for(int k = 0;k < n; k++){
                if(b.mat[j][k] == 0) continue;
                matrix[i][k] = (matrix[i][k]+(ll)a.mat[i][j]*b.mat[j][k])%p;
            }
        }
    }
    for(int i = 0;i < n; i++)
        for(int j = 0;j < n; j++)
            a.mat[i][j] = matrix[i][j];
}
int mp[maxn][maxn];
int main(){
    int t,n,m,q,u,v;
    Node a,b,c;
    scanf("%d",&t);
    while(t--){
        scanf("%d%d%d%d",&n,&m,&p,&q);
        c.init();
        for(int i = 0;i < m; i++){
            scanf("%d%d",&u,&v);
            u--,v--;
            c.mat[u][u]++;
            c.mat[v][v]++;
            c.mat[u][v] = c.mat[v][u] = -1;
        }
        if(q < n-1){
            printf("0\n");
            continue;
        }
        int ans = c.Det(n-1,p);
        if(ans == 0){
            printf("0\n");
            continue;
        }
        a.init();
        b.one(n);
        for(int i = 1;i < n; i++)
            a.mat[i][i] = i,a.mat[i][i-1] = n-i;
        while(q){
            if(q&1) multi(b,a,n);
            multi(a,a,n);
            q/=2;
        }
        ans = (ll)ans*b.mat[n-1][0]%p;
        printf("%d\n",ans);
    }
    return 0;
}

    原文作者:动态规划
    原文地址: https://blog.csdn.net/firenet1/article/details/48931033
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞