矩阵树定理学习笔记+洛谷3317 bzoj3534 SDOI2014 重建 矩阵树定理+期望 +构造

题目链接
题意就是给你n个点,每两个点之间有一条边,这条边存在的概率是 p ,求生成树个数。
我觉得这真是道神题!
首先先介绍一下矩阵树定理,由于我不会,所以没有给任何证明,只给了结论,想知道证明请自行搜索。矩阵树定理可以求一个无向图的生成树个数(似乎有向的也可以求,但是我还不会)。它的做法是这样的:用邻接矩阵存边, a i j ( i j ) 的值为点 i 到点 j 的边数的相反数, a i i 的值为点 i 的度数。生成树个数就是任意一个余子式 M i i 的值,余子式 M i j 是一个行列式去掉第 i 行第 j 列后的结果。求行列式的值可以用高斯消元,把行列式消成阶梯型的(上三角),最终的答案是行列式对角线上所有数的乘积。
说明一下要用到的两种行列式的初等变换:
1.互换行列式的两行,行列式的值变为原来的相反数
2.将行列式一行的若干倍加到另一行,行列式的值不变
这两种变换保证了在高斯消元时只有互换两行时会改变原行列式的值,我们在进行消元的过程中只需要再记录一下改变次数的奇偶即可。
我一开始就跪了!我一开始以为答案是

T r e e e p e
,也就是直接把基尔霍夫矩阵中的度数矩阵中 a i j ( i j ) 设为 i j 有边的期望,然后 a i i 设为除了 i 本身的所有点与它之间有连边的期望之和,然后做矩阵树定理,于是我发现根本过不了样例。看了题解后我真的不由得感叹这题的绝妙。我们来看一下它的正确做法。
首先,第一个妙处在于对于任意一棵生成树,它存在的条件应该是所有在这棵树上的边都存在,所有不在这棵树上的边都不存在。那么根据乘法原理,任意一棵生成树存在的概率应该是
e T r e e p e e T r e e ( 1 p e )
所有我们要求的应该是
T r e e e T r e e p e e T r e e ( 1 p e )
我们发现好像并不知道该怎么样直接求出这个式子的值。但是我们发现,用我之前那种错误的做法,我们可以得到 T r e e e p e 的值,那么现在开始了本题的又一个神奇之处了:构造!我们考虑去构造这个答案。我们让原来的 p i j 变为 p i j 1 p i j ( i j ) ,令 p i i = j = 1 n p i j ( i j ) ,由于我们是去构造最终答案,所以并不需要考虑原行列式的值发生了怎样的变化。
另外我们在高斯消元之前还要记录下 e ( 1 p e )
进行高斯消元并按照矩阵树定理计算答案的话,我们这时就会得到
T r e e e T r e e p e 1 p e
那么我们再乘上之前记录的 e ( 1 p e ) ,会得到
e ( 1 p e ) T r e e e T r e e p e 1 p e
由乘法分配律,我们可以得到
T r e e e ( 1 p e ) e T r e e p e 1 p e
T r e e e T r e e p e e T r e e p e 1 p e
然后我们发现我们已经构造出来前面的答案啦!
另外补充一个小技巧:我们为了防止数据中因为存在 p e = 1 ,从而导致 p e 1 p e 分母是 0 而RE,我们可以进行特判,如果 f a b s ( 1.0 p e e p s ) ,就让 p e = e p s
最后是代码:

#include <bits/stdc++.h>
using namespace std;

int n,cnt;
double a[55][55],eps=1e-10,ans=1,ji=1;
void gauss()
{
    for(int i=1;i<=n;++i)
    {
        int r=i;
        for(int j=i+1;j<=n;++j)
        {
            if(fabs(a[j][i])>fabs(a[r][i]))
            r=j;
        }
        if(fabs(a[r][i])<eps)
        {
            ans=0;
            return;
        }
        if(r!=i)
        {
            ++cnt;
            swap(a[r],a[i]);
        }
        for(int j=i+1;j<=n;++j)
        {
            double t=a[j][i]/a[i][i];
            for(int k=1;k<=n;++k)
            a[j][k]-=t*a[i][k];
        }
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
    {
        for(int j=1;j<=n;++j)
        {
            scanf("%lf",&a[i][j]);
            if(i!=j)
            {
                if(fabs(1.0-a[i][j])<eps)
                a[i][j]-=eps;
                if(i<j)         
                ji*=(1-a[i][j]);                
                a[i][j]/=(1.0-a[i][j]);
                a[i][j]=-a[i][j];
            }
        }           
    }   
    for(int i=1;i<=n;++i)
    {
        for(int j=1;j<=n;++j)
        {
            if(i==j)
            continue;
            a[i][i]+=a[i][j];
        }
        a[i][i]=-a[i][i];
    }
    --n;
    gauss();
    for(int i=1;i<=n;++i)
    ans*=a[i][i];
    if(cnt%2)
    ans*=-1;
    ans*=ji;
    printf("%.8lf\n",ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/forever_shi/article/details/80643947
今日推荐