[ZJOI2016]小星星

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

题面

[ZJOI2016]小星星

题解

就是对于节点数相同的一棵树和一张图,对于这颗树的点集的子集向这幅图的点集的子集建立一个映射,且满足在树上的每一条边的两个端点映射到图中时也有边连接,问这样的映射方式有多少种。
我们设 \(f[x][j]\) 表示以 \(x\) 为根的子树,当 \(x\) 映射成 \(j\) 时的方案数。
所以我们可以先直接枚举点集的子集。
然后在 dfs 这棵树的时候,对于边 \((x,y)\) ,即某种映射 $x -> i,y -> j $ 我们直接判断在图中是存边 \((i,j)\) 即可。
对于子节点 \(y\) 的所有映射的方案之间显然是累加关系。
而子节点与父节点的方案之间显然是乘法关系。
所以转移关系便很清晰了。
总的时间复杂度是 \(O(n ^ 3 2 ^ n)\),理论可以过,不过需要卡常,不过显然我没有卡过。
以下代码需要吸氧,但是吸了之后快了 \(4\) 秒就很离谱。

代码

#include<cstdio>
#include<vector>

using namespace std;

#define re register

typedef long long LL;

int n, m, q[25], tot; bool g[20][20];

vector < int > to[20]; LL ans = 0, f[20][20];

inline void add(int u, int v) { to[u].push_back(v); to[v].push_back(u); }

void dfs(re int x, re int fa) {
	for(re unsigned int i = 0; i < to[x].size(); i++)
		if(to[x][i] != fa) dfs(to[x][i], x);
	for(re int i = 1; i <= tot; i++) {
		f[x][q[i]] = 1;
		for(re unsigned int p = 0; p < to[x].size(); p++) {
			if(to[x][p] == fa) continue; re LL sum = 0;
			for(re int j = 1; j <= tot; j++)
				if(g[q[i]][q[j]]) sum += f[to[x][p]][q[j]];
			f[x][q[i]] *= sum;
		}
	}
}

inline void solve() {
	re int lim = 1 << n;
	for(re int i = 0; i < lim; i++) {
		tot = 0;
		for(re int j = i, k = 1; j; j >>= 1, k++)
			if(j & 1) q[++tot] = k;
		dfs(1, 0);
		for(re int j = 1; j <= tot; j++)
			ans += (((n - tot) & 1) ? -1 : 1) * f[1][q[j]];
	}
}

int main() {
	scanf("%d%d", &n, &m);
	for(re int i = 1, u, v; i <= m; i++) scanf("%d%d", &u, &v), g[u][v] = g[v][u] = 1;
	for(re int i = 1, u, v; i <  n; i++) scanf("%d%d", &u, &v), add(u, v);
	solve(); printf("%lld\n", ans);
	return 0;
}

原文地址:https://www.cnblogs.com/sjzyh/p/15085264.html