题目链接
题意就是给你n个点,每两个点之间有一条边,这条边存在的概率是
,求生成树个数。
我觉得这真是道神题!
首先先介绍一下矩阵树定理,由于我不会,所以没有给任何证明,只给了结论,想知道证明请自行搜索。矩阵树定理可以求一个无向图的生成树个数(似乎有向的也可以求,但是我还不会)。它的做法是这样的:用邻接矩阵存边,
的值为点
到点
的边数的相反数,
的值为点
的度数。生成树个数就是任意一个余子式
的值,余子式
是一个行列式去掉第
行第
列后的结果。求行列式的值可以用高斯消元,把行列式消成阶梯型的(上三角),最终的答案是行列式对角线上所有数的乘积。
说明一下要用到的两种行列式的初等变换:
1.互换行列式的两行,行列式的值变为原来的相反数
2.将行列式一行的若干倍加到另一行,行列式的值不变
这两种变换保证了在高斯消元时只有互换两行时会改变原行列式的值,我们在进行消元的过程中只需要再记录一下改变次数的奇偶即可。
我一开始就跪了!我一开始以为答案是
首先,第一个妙处在于对于任意一棵生成树,它存在的条件应该是所有在这棵树上的边都存在,所有不在这棵树上的边都不存在。那么根据乘法原理,任意一棵生成树存在的概率应该是
另外我们在高斯消元之前还要记录下 。
进行高斯消元并按照矩阵树定理计算答案的话,我们这时就会得到
另外补充一个小技巧:我们为了防止数据中因为存在 ,从而导致 分母是 而RE,我们可以进行特判,如果 ,就让 。
最后是代码:
#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;
}