[SDOI2014] 重建(矩陣樹定理推廣)

文章目錄

題目

[SDOI2014] 重建

分析

問題要求 T(eTpeeT(1pe))\sum_{T} \left( \prod_{e \in T} p_e \prod_{e \notin T} (1 - p_e)\right) 由於矩陣樹只能處理 eTe \in T,所以把式子變成 T(eTpee(1pe)eT(1pe))=e(1pe)T(eTpe1pe)\begin{aligned} & \sum_{T} \left( \prod_{e \in T} p_e \frac{\prod_{e} (1 - p_e)}{\prod_{e \in T} (1 - p_e)} \right) \\ =& \prod_{e} (1 - p_e) \sum_{T} \left( \prod_{e \in T} \frac{p_e }{1 - p_e} \right)\end{aligned} 於是用矩陣樹定理推廣計算 T(eTpe1pe)\sum_{T} \left( \prod_{e \in T} \frac{p_e }{1 - p_e} \right),最後乘上 e(1pe)\prod_{e} (1 - p_e) 即爲答案。
注意 pe=1p_e = 1 會除零而出問題,給 pep_e 減個 ϵ\epsilon 即可。


矩陣樹定理推廣:計算所有生成樹邊權乘積的和,即 T(eTxe)\sum_{T} \left( \prod_{e \in T} x_e \right) 其中 xex_e 爲一個有理數。

xe=petx_e = \dfrac{p_e}{t},其中 pe,tZp_e, t \in \Z,那麼 T(eTxe)=1tn1T(eTpe)\begin{aligned} & \sum_{T} \left( \prod_{e \in T} x_e \right) \\ =& \frac{1}{t^{n - 1}} \sum_{T} \left( \prod_{e \in T} p_e \right)\end{aligned} 當邊權 pp爲整數的時候很好計算:把 u,vu, v 之間連上 pp 條邊,然後計算生成樹的個數就是原圖所有生成樹邊權乘積和。

但是 tn1t^{n - 1} 很顯然精度不夠,所以我們用 行列式的可乘性tn1t^{n - 1} 分配給行列式中每個元素,於是我們有了一個新的基爾霍夫矩陣:度爲連出去所有邊的邊權和,邊權爲原邊權的相反數。然後正常矩陣樹定理得到的答案就是所求的了。

代碼

#include <bits/stdc++.h>

const int MAXN = 50;
const double eps = 1e-5;

int N;
double Mat[MAXN + 5][MAXN + 5];

void AddEdge(int u, int v, double w) {
    Mat[u][u] += w, Mat[v][v] += w;
    Mat[u][v] -= w, Mat[v][u] -= w;
}

inline double Abs(const double &x) {
    return x < 0 ? -x : x;
}

inline int Comp(const double &x, const double &y) {
    if (Abs(x - y) < eps)
        return 0;
    return x > y ? 1 : -1;
}

double Det(int n) {
    n--;
    double ret = 1;
    for (int i = 1; i <= n; i++) {
        int p = i;
        for (int j = i; j <= n; j++)
            if (Comp(Mat[p][i], Mat[j][i]) < 0)
                p = j;
        if (p != i)
            std::swap(Mat[i], Mat[p]);
        if (!Comp(Mat[i][i], 0))
            return 0;
        ret *= Mat[i][i];
        for (int j = i + 1; j <= n; j++) {
            double tmp = Mat[j][i] / Mat[i][i];
            for (int k = i; k <= n; k++)
                Mat[j][k] -= Mat[i][k] * tmp;
        }
    }
    return Abs(ret);
}

int main() {
    scanf("%d", &N);
    double All = 1.0;
    for (int i = 1; i <= N; i++)
        for (int j = 1; j <= N; j++) {
            double p; scanf("%lf", &p);
            if (i < j) {
                if (Comp(p, 1) == 0)
                    p -= eps;
                AddEdge(i, j, p / (1 - p));
                All *= (1 - p);
            }
        }
    printf("%.5f", Det(N) * All);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章