[SHOI2016] 黑暗前的幻想乡

容斥原理。

首先看到题意,发现必然需要求一系列图的生成树数量,因此想到先引入矩阵树定理来求解。

然后考虑如何求得满足限制的方案数。

直接算需要考虑到之前哪个公司已经修了边,修了哪些边,很难做。

可以容斥,先求出所有可以形成的生成树的总数。

此时会发现明显算多了,刚好由 n - 1 个公司建造的生成树是统计上了,但是也统计上了刚好由 n - 2 个公司建造的生成树。

这时可以枚举到底是哪 n - 2 个公司建造了这个树,建图的时候只加入这n-2个公司的边,对着这个图跑一边矩阵树,然后依次减去这些集合的方案数。

以此类推,设点集边集形成的图的集合为 S,如下式。

\(ans = \sum_{T \subseteq S} (-1)^{|T| + 1} * Matrix\_tree(T)\)

很典型的容斥式子。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

#define u first
#define v second

const int maxn = 20;
const int mod = 1000000000 + 7;
int n, m[maxn], a[maxn][maxn], ans;

std::vector > edge[maxn];

inline int Fast_pow(int x, int p) {
  int res = 1;
  for( ; p; x = 1ll * x * x % mod, p = p >> 1) if( p & 1 ) res = 1ll * x * res % mod;
  return res;
}

inline int Gauss(int len) {
  int res = 1;
  for(int i = 1; i <= len; ++i) for(int j = i + 1; j <= len; ++j) {
    if( a[i][i] == 0 ) return 0;
    if( a[j][i] ) {
      int tmp = 1ll * a[j][i] * Fast_pow(a[i][i], mod - 2) % mod;
      for(int k = i; k <= len; ++k) a[j][k] = (a[j][k] - 1ll * tmp * a[i][k] % mod + mod) % mod;
    }
  }
  for(int i = 1; i <= len; ++i) res = 1ll * res * a[i][i] % mod;
  return res;
}

int main(int argc, char const *argv[])
{
  scanf("%d", &n);
  for(int i = 1; i < n; ++i) {
    scanf("%d", m + i);
    for(int x, y, j = 1; j <= m[i]; ++j) scanf("%d%d", &x, &y), edge[i].push_back(make_pair(x, y));
  }
  for(int f = 0, i = 0; i < (1 << (n - 1)); ++i) {
    memset(a, 0, sizeof a), f = 1;
    for(int j = 1; j < n; ++j) if( i & (1 << (j - 1)) ) {
      for(int k = 0; k < m[j]; ++k) {
        --a[edge[j][k].u][edge[j][k].v], --a[edge[j][k].v][edge[j][k].u];
        ++a[edge[j][k].u][edge[j][k].u], ++a[edge[j][k].v][edge[j][k].v];
      }
    } else f = -f;
    for(int j = 1; j <= n; ++j) for(int k = 1; k <= n; ++k) if( a[j][k] < 0 ) a[j][k] = a[j][k] + mod;
    ans = ((ans + f * Gauss(n - 1)) % mod + mod) % mod;
  }
  printf("%d\n", ans);

  return 0;
}

你可能感兴趣的:([SHOI2016] 黑暗前的幻想乡)