题解[SHOI2016]黑暗前的幻想乡

矩阵树定理+容斥

题意

给定 \(n-1\) 个集合 \(S\),第 \(i\) 个集合里包含 \(m_i\) 条边,你需要从这 \(n-1\) 个集合里各选一条边,构成一棵树,问一共有多少种方案

题解

很明显题目所求的方案个数就是生成树个数,那么就可以用矩阵树定理来做

但题目又给了限制,即每个集合里只能选一条边,这个用矩阵树定理就没法做了

考虑容斥,令 \(f(A)\) 表示集合 \(A\) 中的边能够构成的生成树数量, \(g(i)\) 表示从集合 \(S\) 中任意 \(i\) 个不同集合的并集

那么答案就等于 \(\sum\limits_{i=0}^{n-1}(-1)^if(g(n-1-i))\)

说人话就是 \(n-1\) 个集合构成的无向图的生成树数量 \(- (n-2)\) 个集合构成的生成树数量 \(+(n-3)\) 个集合构成的生成树数量 ...

考虑 \(n\) 比较小,直接搜索就行

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#define x first
#define y second
using namespace std;
const int MOD = 1e9 + 7;
typedef long long ll;

pair<int, int> edge[2505];
int head[21], net[2505], ver[2505], idx;
int n, a[21][21], tot;
bool is[21];

void add(int a, int b)
{
    net[++idx] = head[a], ver[idx] = b, head[a] = idx;
}

int qmi(int a, int b)
{
    int res = 1;
    while (b)
    {
        if (b & 1)
            res = (ll)res * a % MOD;
        a = (ll)a * a % MOD;
        b >>= 1;
    }
    return res;
}

int work()
{
    int res = 1, w = 1;
    for (int i = 1; i < n; i++)
    {
        for (int j = i + 1; j < n; j++)
        {
            if (a[j][i] && !a[i][i])
            {
                swap(a[j], a[i]), w = -w;
                break;
            }
        }
        int inv = qmi(a[i][i], MOD - 2);
        for (int j = i + 1; j < n; j++)
        {
            int temp = (ll)a[j][i] * inv % MOD;
            for (int k = i; k < n; k++)
                a[j][k] = (a[j][k] - (ll)a[i][k] * temp % MOD) % MOD;
        }
    }
    for (int i = 1; i < n; i++)
        res = (ll)res * a[i][i] % MOD;
    res *= w;
    return (res % MOD + MOD) % MOD;
}

int dfs(int ned, int last)
{
    if (ned == 0)
    {
        memset(a, 0, sizeof(a));
        int t = 0;
        for (int i = 1; i < n; i++)
        {
            if (is[i])
                continue;
            t++;
            for (int j = head[i]; j; j = net[j])
            {
                int v = ver[j];
                int xx = edge[v].x, yy = edge[v].y;
                a[xx][xx]++, a[yy][yy]++;
                a[xx][yy]--, a[yy][xx]--;
            }
        }
        return work();
    }
    int res = 0;
    for (int i = last; i < n; i++)
    {
        if (is[i])
            continue;
        is[i] = true;
        res = ((ll)res + dfs(ned - 1, i)) % MOD;
        is[i] = false;
    }
    return res;
}

int main()
{
    scanf("%d", &n);
    for (int i = 1; i < n; i++)
    {
        int m;
        scanf("%d", &m);
        for (int j = 1; j <= m; j++)
        {
            int u, v;
            scanf("%d%d", &u, &v);
            edge[++tot] = make_pair(u, v);
            add(i, tot);
        }
    }
    int ans = 0, w = 1;
    for (int i = 0; i < n; i++)
        ans = (ans + dfs(i, 1) * w) % MOD, w = -w;        
    printf("%d", (ans % MOD + MOD) % MOD);
    return 0;
}
posted @ 2021-05-08 16:00  DSHUAIB  阅读(49)  评论(0编辑  收藏  举报