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;
}

posted @ 2020-09-26 09:07  With_penguin  阅读(91)  评论(0编辑  收藏  举报