#
给定无向图,每条边以 的概率存在。求存在的边构成一棵生成树的概率。
点数 ,无重边。
#
前置知识:矩阵树定理 - OI Wiki ↗,行列式计算 - OI Wiki ↗,题解同步发表于 TonyYin’s Blog ↗。
看到 的范围和生成树,容易想到矩阵树定理。
矩阵树定理#
对于一个无向图 ,设边 ,其边权为 .
定义邻接矩阵 ,.
定义度数矩阵 ,,存储每个点的度数。
定义基尔霍夫矩阵 ,.
任取 ,将基尔霍夫矩阵删去第 行和第 列,构成一个 的子矩阵,不妨记为 .
根据无向图的矩阵树定理,有:
对矩阵 计算行列式,得到的结果即为:每棵生成树中,所有边权乘积的和。
分析#
由概率的性质,我们最终求的答案可以表示为:
这里一定不要忘记乘上:.
注意到 的表示式与矩阵树定理很像,考虑如何将其转化成矩阵树定理的标准形式。
要转化为标准形式,我们需要去掉 这个部分,具体转化步骤:
注意到分子部分为常数,于是:
这样,只需要将边权定为 ,之后利用矩阵树定理求解即可。
#
实现时,为了防止边权的分母为 ,若 ,将 即可避免问题发生。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100;
const double eps = 1e-10;
int n;
double g[MAXN][MAXN], A[MAXN][MAXN];
double det() {
for(int col = 2; col <= n; col++) {
int max_row = col;
for(int i = col + 1; i <= n; i++)
if(A[i][col] > A[max_row][col]) max_row = i;
if(max_row != col) swap(A[col], A[max_row]);
for(int row = col + 1; row <= n; row++) {
double t = A[row][col] / A[col][col];
for(int i = 2; i <= n; i++) {
A[row][i] -= A[col][i] * t;
}
}
}
double ans = 1.0;
for(int i = 2; i <= n; i++) ans *= A[i][i];
return ans;
}
int main() {
scanf("%d", &n);
double prod = 1.0;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
scanf("%lf", &g[i][j]); g[i][j] -= eps;
A[i][j] -= g[i][j] / (1.0 - g[i][j]);
}
}
for(int i = 1; i <= n; i++) {
for(int j = i + 1; j <= n; j++) {
A[i][i] += g[i][j] / (1.0 - g[i][j]);
A[j][j] += g[i][j] / (1.0 - g[i][j]);
prod *= (1.0 - g[i][j]);
}
}
cout << prod * det() << endl;
return 0;
}
cpp