[ZJOI2016]小星星(容斥+dp)

时间:2021-04-07
本文章向大家介绍[ZJOI2016]小星星(容斥+dp),主要包括[ZJOI2016]小星星(容斥+dp)使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

题目:洛谷P3349LOJ#2091

题目描述:

给一棵\(n\)个点的树以及一个\(n\)个点,\(m\)条边的图,问有多少种排列\(p\)使得对于任意\(i,j\)如果\(p_{i}\)\(p_{j}\)在树上有边,那么\(i\)\(j\)在图上一定也有边

\(n \leq 17\)\(m \leq \frac{n \cdot (n-1)}{2}\)

蒟蒻题解:

容易想到暴力dp,设\(f[i][j][k]\)表示以\(i\)为根,\(i\)对应图上的点\(j\),且\(i\)子树内的点对应图上的点集为\(k\),加入\(i\)的一个儿子\(u\)

\[f[i][j][k] = \sum_{t \subseteq s \subseteq k}(f[i][j][s] \cdot f[u][t][k \bigotimes s]) \]

但是这样复杂度是\(\theta(n^{3} \cdot 3^{n})\)的,会\(T\)

尝试优化掉第三维\(dp\),如果把第三维去掉会出现树上多个点对应图上相同的节点的情况,还要减去重复的情况

考虑容斥,先枚举一个集合\(S\),然后树上每个点都要属于集合上的点,设\(f[i][j]\)表示以\(i\)为根,\(i\)对应图上的点\(j\)的方案数,则加入\(i\)的一个儿子\(u\)

\[f[i][j] = \sum_{k = 1}^{k \leq n}(f[i][j] \cdot f[u][k]) \]

最终的答案为:

\[ans=\sum_{|S|=n}f[1][i]-\sum_{|S|=n-1}f[1][i]+\sum_{|S|=n-2}f[1][i]-...\pm\sum_{|S|=1}f[1][i] \]

参考程序:

#include<bits/stdc++.h>
using namespace std;
#define Re register int
typedef long long ll;

const int M = 280;
int n, m, cnt, hea[20], nxt[M], to[M];
ll s, ans, g[20], f[20][20];
bool p[20], b[20][20];

inline int read()
{
	char c = getchar();
	int ans = 0;
	while (c < 48 || c > 57) c = getchar();
	while (c >= 48 && c <= 57) ans = (ans << 3) + (ans << 1) + (c ^ 48), c = getchar();
	return ans;
}

inline void add(int x, int y)
{
	nxt[++cnt] = hea[x], to[cnt] = y, hea[x] = cnt;
}

inline void dfs(int x, int fa)
{
	for (Re i = 1; i <= n; ++i) f[x][i] = p[i];
	for (Re i = hea[x]; i; i = nxt[i])
	{
		int u = to[i];
		if (u == fa) continue;
		dfs(u, x);
		for (Re j = 1; j <= n; ++j)
			if (p[j])
			{
				ll s = 0;
				for (Re k = 1; k <= n; ++k)
					if (p[k] && b[j][k]) s += f[u][k];
				f[x][j] *= s;
			}
	}
}

inline void calc(int x)
{
	dfs(1, 0);
	s = 0;
	for (Re i = 1; i <= n; ++i)
		if (p[i]) s += f[1][i];
	((n - x) & 1) ? ans -= s : ans += s;
}

inline void sea(int x, int y)
{
	if (x == n + 1)
	{
		calc(y);
		return;
	}
	p[x] = 0, sea(x + 1, y);
	p[x] = 1, sea(x + 1, y + 1);
}

int main()
{
	n = read(), m = read();
	for (Re i = 0; i < m; ++i)
	{
		int u = read(), v = read();
		b[u][v] = b[v][u] = 1;
	}
	for (Re i = 1; i < n; ++i)
	{
		int u = read(), v = read();
		add(u, v), add(v, u);
	}
	sea(1, 0);
	printf("%lld", ans);
	return 0;
}

原文地址:https://www.cnblogs.com/clfzs/p/14629168.html