luogu P3349 [ZJOI2016]小星星
发现要求的一一映射的条件可以转化为,图中的每个点至少被树上的点映射了一次。考虑对此进行容斥,每次限定一个集合内的数不可被映射,最后乘上容斥系数。
设 \(f[x][i]\) 表示树上 \(x\) 被映射到 \(i\) 的方案数,则转移为:
\[f[x][i]=\prod \limits_{v\in son}\sum\limits_{j\in S}map[i][j]\times f[v][j]
\]
其中 \(S\) 表示没有被限制的集合, \(map\) 的图的邻接矩阵。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#define int long long
using namespace std;
const int N = 20;
int n, m, head[N], cnt, map[N][N], f[N][N];
struct Edge
{
int nxt, to;
}g[N * N];
void add(int from, int to)
{
g[++cnt].nxt = head[from];
g[cnt].to = to;
head[from] = cnt;
}
void init()
{
scanf("%lld %lld", &n, &m);
for (int i = 1, x, y; i <= m; i++)
scanf("%lld %lld", &x, &y), map[x][y] = map[y][x] = 1;
for (int i = 1, x, y; i < n; i++)
scanf("%lld %lld", &x, &y), add(x, y), add(y, x);
}
void calc(int x, int fa, int S)
{
for (int i = 1; i <= n; i++)
if ((S & (1 << i - 1)) == 0)
f[x][i] = 1;
else
f[x][i] = 0;
for (int i = head[x]; i; i = g[i].nxt)
{
int v = g[i].to;
if (v == fa) continue;
calc(v, x, S);
for (int j = 1; j <= n; j++)
{
if ((S & (1 << j - 1))) continue;
int tmp = 0;
for (int k = 1; k <= n; k++)
{
if ((S & (1 << k - 1)) || !map[j][k]) continue;
tmp += f[v][k];
}
f[x][j] *= tmp;
}
}
}
void work()
{
int ans = 0, MaxN = 1 << n;
for (int i = 0; i < MaxN; i++)
{
calc(1, -1, i);
int sum = 0, tmp = 0;
for (int j = 1; j <= n; j++)
if ((i & (1 << j - 1))) tmp++;
else sum += f[1][j];
tmp = (tmp & 1) ? -1 : 1;
ans = (ans + tmp * sum);
}
printf("%lld\n", ans);
}
signed main()
{
init();
work();
return 0;
}
由于博主比较菜,所以有很多东西待学习,大部分文章会持续更新,另外如果有出错或者不周之处,欢迎大家在评论中指出!