[SHOI 2016] 黑暗前的幻想乡(矩阵树定理 + 容斥) | 错题本

文章目录

题目

[SHOI2016] 黑暗前的幻想乡

分析

把问题转化为“每个公司都要参与”的方案数。
假设现在只有 3 个公司,我们先算总方案数,减掉 1 公司不参与的方案数,减掉 2 公司不参与的方案数,减掉 3 公司不参与的方案数,这时候还不是答案,因为我们把 1、2 公司都不参与的方案数,2、3 公司都不参与的方案数和 3、4 公司都不参与的方案数多减了一次(例如:减掉 1 公司不参与的方案数、减掉 2 公司不参与的方案数后,1、2 公司都不参与的方案数被减了两次),所以要把它们加回来。
发现这是一个容斥。

也可以这样理解:
n n 是公司数, A i A_i 代表 i i 号公司不参与建设的情况,不妨设 n n 为偶数,根据容斥原理: A 1 A 2 A n = 1 i n A i 1 i < j n A i A j + A 1 A 2 A n |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| p p 是总方案数,于是要求的就是 p A 1 A 2 A n =   p ( 1 i n A i 1 i < j n A i A j + A 1 A 2 A n ) \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 ( 2 n n 3 log 2 ( 1 0 9 + 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;
}

猜你喜欢

转载自blog.csdn.net/C20190102/article/details/107143021