容斥
第一题:
ZJOI小星星
题目链接:https://www.luogu.org/problem/P3349
题目大意:给定n个点m条边的无向图,和一个n个点的树,求出有多少个从树到图的映射。n<=17,m<=n*(n-1)/2
方法:先不考虑重复映射的情况。定义f[u][i]为把树上的节点u映射到图中的节点i的方案数。转移方程如下:
\[f[u][i] = \prod_{v\in son(u)} \sum_{j\in son(i)} f[v][j]
\]
然后答案为
\[\prod_{i = 0}^{n-1} f[root][i]
\]
然而题目要求必须一一映射,不能重复映射。考虑用容斥。
最终答案为能够映射到所有节点的答案,减去能够映射到某(n-1)个节点的方案,加上能够映射到某(n-2)个节点的方案,以此类推。复杂度\(O(2^n * n^3)\)
代码:
#include <iostream>
#include <bitset>
#include <vector>
#include <cstring>
using namespace std;
#define DEBUG 0
#define MAXN 18
typedef long long LL;
struct GRAPH {
vector<vector<int> > s;
void ClearEdges() {
for (auto& i : s)
i.resize(0);
}
void Init(int n) {
ClearEdges();
s.resize(n+1);
}
void AddUndi(int u, int v) {
s[u].push_back(v);
s[v].push_back(u);
}
};
GRAPH g, G;
bitset<MAXN> ban;
int n;
LL f[MAXN][MAXN];
bool vis[MAXN];
void dfs(int u) {
vis[u] = true;
for (int i = 0; i < n; ++i) {
f[u][i] = ban[i] ? 0 : 1;
}
for (int v : g.s[u]) {
if (vis[v]) continue;
dfs(v);
for (int i = 0; i < n; ++i) {
if (ban[i]) continue;
LL res = 0;
for (int j : G.s[i]) {
if (ban[j]) continue;
res += f[v][j];
}
f[u][i] *= res;
}
}
}
int main() {
int m;
cin >> n >> m;
g.Init(n);
G.Init(n);
while (m--) {
int u, v;
cin >> u >> v;
--u;
--v;
G.AddUndi(u, v);
}
m = n-1;
while (m--) {
int u, v;
cin >> u >> v;
--u;
--v;
g.AddUndi(u, v);
}
LL ans = 0;
for (int s = 0, maxs = 1 << n; s < maxs; ++s) {
memset(vis, 0, n * sizeof(bool));
ban = s;
dfs(0);
LL now = 0;
for (int j = 0; j < n; ++j) {
now += f[0][j];
}
if (ban.count() & 1)
ans -= now;
else
ans += now;
#if DEBUG
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
cout << f[i][j] << ' ';
}
cout << endl;
}
cout << endl;
#endif
}
cout << ans << endl;
return 0;
}
第二题:
luogu 4336 SHOI2016黑暗前的幻想乡
题意:有n个点(n <= 17),(n-1)家公司,第i家公司能建造\(m_i\)条路,求使得每家公司建一条边,使得图连通的方案数。
思路:首先考虑所有公司都能参与,然后使图连通的方案数。这些方案里包含了一些某些公司没有参与的方案。所以减去有一家公司没有参与的方案数。然后加上有两家公司没有参与的方案数。以此类推,最后得到的就是每家公司都参与了的方案数。复杂度\(O(2^{n-1} n^3)\)
代码:
#include <cstdio>
#include <vector>
using namespace std;
#define MAXN 21
typedef long long LL;
const int p = 1000000007;
template <typename T>
T Sub(T x, int p) {
return x < 0 ? x + p : x;
}
template <typename T>
void SubMod(T& x, int p) {
if (x < 0)
x += p;
}
template <typename T>
void AddMod(T& x, int p) {
if (x >= p)
x -= p;
}
//x < p
//p must be prime
template <typename T>
T Inv(T x, int p) {
return 1 == x ? 1 : (LL)(p - p / x) * Inv(p % x, p) % p;
}
//simple
struct MATRIX {
const static int maxr = MAXN, maxc = MAXN;
int row, col;
int s[maxr][maxc];
void clear() {
for (int i = 0; i < row; ++i)
for (int j = 0; j < col; ++j)
s[i][j] = 0;
}
//O(n^3)
int det_laplacian() {
int ans = 1;
for (int i = 0; i < row && ans; ++i) {
if (0 == s[i][i]) return 0;
int invi = Inv(s[i][i], p);
for (int k = i + 1; k < row; ++k) {
if (0 == s[k][i]) continue;
int d = (LL)s[k][i] * invi % p;
for (int j = i; j < col; ++j) {
s[k][j] = (s[k][j] - (LL)s[i][j] * d) % p;
}
}
ans = (LL)ans * s[i][i] % p;
}
return Sub(ans, p);
}
};
int main() {
int n;
static vector<pair<int, int> > es[MAXN];
static MATRIX ma;
scanf("%d", &n);
--n;
ma.row = ma.col = n;
for (int i = 0; i < n; ++i) {
int m;
scanf("%d", &m);
while (m--) {
int u, v;
scanf("%d%d", &u, &v);
--u;
--v;
es[i].emplace_back(u, v);
}
}
int ans = 0;
for (int s = 0, maxs = 1 << n; s < maxs; ++s) {
ma.clear();
for (int i = 0; i < n; ++i) {
if (0 == (s & (1 << i))) {
for (auto e : es[i]) {
--ma.s[e.first][e.second];
--ma.s[e.second][e.first];
++ma.s[e.first][e.first];
++ma.s[e.second][e.second];
}
}
}
int t = ma.det_laplacian();
if (__builtin_popcount(s) & 1) {
SubMod(ans -= t, p);
} else {
AddMod(ans += t, p);
}
}
printf("%d\n", ans);
return 0;
}