[SHOI 2016] 黑暗前的幻想鄉(矩陣樹定理 + 容斥) | 錯題本

題目

[SHOI2016] 黑暗前的幻想鄉

分析

把問題轉化爲“每個公司都要參與”的方案數。
假設現在只有 3 個公司,我們先算總方案數,減掉 1 公司不參與的方案數,減掉 2 公司不參與的方案數,減掉 3 公司不參與的方案數,這時候還不是答案,因爲我們把 1、2 公司都不參與的方案數,2、3 公司都不參與的方案數和 3、4 公司都不參與的方案數多減了一次(例如:減掉 1 公司不參與的方案數、減掉 2 公司不參與的方案數後,1、2 公司都不參與的方案數被減了兩次),所以要把它們加回來。
發現這是一個容斥。

也可以這樣理解:
nn 是公司數,AiA_i 代表 ii 號公司不參與建設的情況,不妨設 nn 爲偶數,根據容斥原理:A1A2An=1inAi1i<jnAiAj+A1A2An|A_1 \cup A_2 \cup \cdots \cup A_{n}| = \sum_{1 \leq i \leq n} |A_i| - \sum_{1 \leq i < j \leq n} |A_i \cap A_j| + \cdots - |A_1 \cap A_2 \cap \cdots \cap A_n| pp 是總方案數,於是要求的就是 pA1A2An= p(1inAi1i<jnAiAj+A1A2An)\begin{aligned} & p - |A_1 \cup A_2 \cup \cdots \cup A_{n}| \\ =\ & p - (\sum_{1 \leq i \leq n} |A_i| - \sum_{1 \leq i < j \leq n} |A_i \cap A_j| + \cdots - |A_1 \cap A_2 \cap \cdots \cap A_n|)\end{aligned} 爆算即可,複雜度 O(2nn3log2(109+7))O(2^n \cdot n^3 \cdot \log_2 (10^9 + 7))

錯因

  • 沒做過幾道容斥的題,不太熟悉思路;
  • 高斯消元又忘了換行的時候答案取相反數!

代碼

#include <bits/stdc++.h>

typedef std::pair<int, int> PII;

const int MAXN = 17;
const int MOD = 1000000007;

int N;
int M[MAXN + 5];
PII E[MAXN + 5][MAXN * MAXN + 5];

inline int Add(int x, const int &y) {
    x += y; if (x >= MOD) x -= MOD; return x;
}

inline int Sub(int x, const int &y) {
    x -= y; if (x < 0) x += MOD; return x;
}

inline int Mul(const int &x, const int &y) {
    return (long long)x * y % MOD;
}

int Pow(int x, int y) {
    int ret = 1;
    while (y) {
        if (y & 1)
            ret = Mul(ret, x);
        x = Mul(x, x);
        y >>= 1;
    }
    return ret;
}

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

int Det(int n) {
    n--;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            Mat[i][j] = Sub(Mat[i][j], 0);
    int ret = 1;
    for (int i = 1; i <= n; i++) {
        int p = i;
        for (int j = i + 1; j <= n; j++)
            if (Mat[p][i] < Mat[j][i])
                p = j;
        if (p != i) {
            std::swap(Mat[p], Mat[i]);
            ret = Sub(0, ret);
        }
        if (!Mat[i][i])
            return 0;
        ret = Mul(ret, Mat[i][i]);
        int inv = Pow(Mat[i][i], MOD - 2);
        for (int j = i + 1; j <= n; j++) {
            int tmp = Mul(Mat[j][i], inv);
            for (int k = i; k <= n; k++)
                Mat[j][k] = Sub(Mat[j][k], Mul(Mat[i][k], tmp));
        }
    }
    return ret;
}

void AddEdge(int i) {
    for (int j = 1; j <= M[i]; j++) {
        int u = E[i][j].first, v = E[i][j].second;
        Mat[u][u]++, Mat[v][v]++;
        Mat[u][v]--, Mat[v][u]--;
    }
}

int Tot[MAXN + 5];

int main() {
    scanf("%d", &N);
    for (int i = 1; i <= N - 1; i++) {
        scanf("%d", &M[i]);
        for (int j = 1; j <= M[i]; j++)
            scanf("%d%d", &E[i][j].first, &E[i][j].second);
    }
    int lim = (1 << (N - 1)) - 1;
    for (int S = 1; S <= lim; S++) {
        int cnt = 0;
        for (int i = 1; i <= N; i++)
            for (int j = 1; j <= N; j++)
                Mat[i][j] = 0;
        for (int i = 1; i <= N - 1; i++)
            if (S & (1 << (i - 1))) {
                cnt++;
                AddEdge(i);
            }
        Tot[cnt] = Add(Tot[cnt], Det(N));
    }
    int Ans = 0;
    for (int i = 1; i <= N - 1; i++)
        Ans = ((N - i) & 1) ? Add(Ans, Tot[i]) : Sub(Ans, Tot[i]);
    printf("%d", (Ans % MOD + MOD) % MOD);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章