bzoj 2467 //2467: [中山市选2010]生成树 找规律/矩阵树

bzoj 2467 //2467: [中山市选2010]生成树 //在线测评地址https://www.lydsy.com/JudgeOnline/problem.php?id=2467

更多题解,详见https://blog.csdn.net/mrcrack/article/details/90228694BZOJ刷题记录

找规律

820 kb 12 ms

//2467: [中山市选2010]生成树
//在线测评地址https://www.lydsy.com/JudgeOnline/problem.php?id=2467
//感觉与 bzoj 1002 //1002: [FJOI2007]轮状病毒 很像,搜索网络,确实如此,该好好的学习矩阵树定理了
//找规律,此文思路真不错https://www.luogu.org/problemnew/solution/P4821 作者: zhongzijun 更新时间: 2019-04-14 07:20
/*
//图中一共有4n4n4n个点,5n5n5n条边。根据生成树的性质,我们需要删除n+1条边。
我们再仔细观察,发现它变成一颗树,需要将所有五边形删去一条边,得方案 5 的 n 次方。 然后其中一个五边形要再删去一条边,且这条边必须要在中心圈上。所以剩下一条边只有 4 种方案,得方案 4n5^(n-1)。——zdw
*/
//从思维层面,找规律,要求极高.2019-9-22 7:43
//样例通过,提交AC.2019-9-22 7:59
//
#include
#define mod 2007
int quick_pow(int a,int n){//快速幂
    int ans=1;
    while(n){
        if(n&1)ans=ans*a%mod;
        a=a*a%mod;
        n>>=1;
    }
    return ans;
}
#define mod 2007
int main(){
    int t,n;
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        printf("%d\n",4*n*quick_pow(5,n-1)%mod);
    }
    return 0;
}

矩阵树

1800 kb 452 ms

//矩阵树
//最大的难点在于建树
//此文https://blog.csdn.net/dreaming__ldx/article/details/85626736建树代码写得不错.
//关于矩阵树的使用,此文https://blog.csdn.net/cj1064789374/article/details/85385456介绍得不错,摘抄如下
/*
下面我们介绍一种新的方法——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]表示。


生成树计数

算法步骤:

1、 构建拉普拉斯矩阵

     Matrix[i][j] =

degree(i) , i==j

          -1,i-j有边

           0,其他情况

2、 去掉第r行,第r列(r任意)

3、 计算矩阵的行列式

*/
//关于高斯消元,此文https://blog.csdn.net/u013010295/article/details/47451451思路不错,摘抄如下
/*
有了这个定理,我们就可以轻松解决求无向图生成树个数的一些问题。现在的问题就在于如何求行列式。

显然,按行(列)展开求行列式的效率太低了。我们可以用高斯消元把矩阵消成上三角形式,其行列式就是主对角线上元素的乘积。消元的时间复杂度是O(n3)
的,因此总的时间复杂度就是O(n3)

的。需要注意的是,消元过程中,把一行乘上常数加到另一行上是不改变行列式的,然而交换两行是会改变行列式符号的。我们可以不记录交换两行的次数,只要对最后算出的答案取绝对值即可。

一般来说,我们采用double类型消元可以满足大部分题目的要求,然而有些丧心病狂的题目需要用long double才能AC。(你还记得IEEE 754规定的double有效位数是52位吗?也就是说,1015

内的数double都能精确表示出来,但是中间结果不一定是整数,它们能否精确表示就不好保证了)

值得一提的是,有些题目要求行列式模上一个数的结果。怎么求模意义下的行列式呢?这些题答案都比较大,用浮点数的话精度达不到要求,确实是一个问题。(显然强行用高精度分数类直接消元,最后再取模是可以的,但实现起来就复杂了)

我们注意到最后行列式是主对角线上的元素乘积再取模,根据同余定理,我们只需要对这些元素取模后的结果再相乘,就能得到相同的结果。因此我们可以采用在模意义下对矩阵消元的方法。然而消元过程中我们不可避免地要计算当前列的主元间的比值,这要用到除法;但另一方面,只有加法、减法、乘法操作才能保证同余,怎么在带有除法操作的条件下取模呢?

如果模的数是个质数(其实只需要模数和除数互质),对于除法我们可以直接变成乘上除数的逆元,根据费马小定理,这个逆元可以用快速幂简单求出来。如果模数不是质数,这就比较复杂了,我们在此介绍一种简单的方法。

我们知道,如果对于两个正数,不断地把较大的数减去较小的数,最后一定会有一个数为0
。你可能已经知道,这就是辗转相减法的过程。同样地,我们对于矩阵中的两行,不断地把主元较大的那一行减去主元较小的那一行,最终一定有一行主元为0

,也就是完成了消元(注意这里的减法是模意义下的减法)。而且这一过程是不改变行列式的。

(需要说明的是,一般情况下,矩阵中可能会有主元为负数的情况,这时我们简单的“大数减小数”显然是不行了。你可能会想到,要对正负数的各种情况判断一下,分别改为加法和减法操作。然而,这里我们讨论的是模意义下的矩阵消元,矩阵的元素都是正整数,并不存在这个问题。)

这样的减法效率还不够高,显然,如果两个主元相差太大,我们需要不断地用一行减去另一行。我们可以记录下两个主元相除的商x
(这里用的是整数除法,当不能整除的时候向上向下取整都可以,由于计算机内部的整数除法实现,我们一般是向下取整,而且也符合我们取商的直觉,下面复杂度计算的是向下取整的做法),一次性用主元较大的行减去主元较小的行乘上x倍,这样效率就大大提高了。我们这样做的复杂度是多少呢?其实你也许已经发现,这一过程实际上就是辗转相除法,所以时间复杂度是O(log(n))

的。

(一般的辗转相除法是求两个数a,b
的最大公倍数,它将这个问题转化为求b,amodb的最大公倍数。这里我们对两个主元a,b转化为b,a−(adivb)×b,因为a=(adivb)×b+amodb

,所以这两个过程本质上是一样的。)

需要注意的是,交换两行是会改变行列式符号的,因此在消元过程中我们必须记录进行交换两行的次数的奇偶性,若是奇数,那么实际的行列式除了矩阵主对角线上的元素乘积,还要乘上模意义下的−1
,也就是模数减1。
*/

//编写过程中,不断遇到错误,更改多次,样例通过,提交AC.2019-9-23
#include
#include
#define maxn 500
#define mod 2007
int t,n,a[maxn][maxn],b[maxn];
void add(int u,int v){
    ++a[u][u],++a[v][v],--a[u][v],--a[v][u];
}
void build(){//建立矩阵
    int u;
    memset(a,0,sizeof(a));
    for(u=1;u }
int guass(){
    int i,j,k,ans=1,mul;
    for(i=1;i         for(j=i+1;j             while(a[j][i]){//此处错写成if(a[j][i])//a[j][i]作分母,辗转相减,一定会成为0
                mul=a[i][i]/a[j][i];
                for(k=i;k                 memcpy(b,a[i],sizeof(a[i])),memcpy(a[i],a[j],sizeof(a[j])),memcpy(a[j],b,sizeof(a[j])),ans=-ans;//swap(a[i],a[j]);//此处错写成memcpy(b,a[i],sizeof(a[i])),memcpy(a[j],b,sizeof(a[j])),ans=-ans;
            }
        ans=ans*a[i][i]%mod;
    }
    return (ans+mod)%mod;//此处错写成return ans;
}
int main(){
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n),n<<=2;
        build();
        printf("%d\n",guass());
    }
    return 0;
}

你可能感兴趣的:(跟着大佬学算法)