gmoj 3296 LuoguP3296 [SDOI2013]刺客信条

前言

做法来自:@pzrpzr ,写一下!Orz pzr!

题目大意

\(n\)个点的无根树,每个点有两个\(0/1\)权值,合适地安排节点在同构树中的顺序,使得前后对应的权值不同节点个数最小,并输出。

解题思路

首先,我们套路地将无根树的同构问题转化成以重心为根的有根树的同构问题。(相关证明请查阅相关资料,此处不赘述。)

找出重心,以此为根。若有两个重心,则新增一个点,使其为根,并删去原重心之间的连边,且两个重心分别向根连边。不难发现此节点一定为重心,且不影响答案。

注意到一个条件即一个节点的度数\(<=11\),这启示我们使用dp解决这个问题。

\(f_{x,y}\)表示\(x\)子树与\(y\)子树同构,\(x\)对应\(y\)时,最小的代价。容易发现\(x\)\(y\)的深度、儿子个数、子树大小均应该相等。故真正有用的状态很小。

至于转移,pzr天才地想到不需要通过树hash进行判断同构。如果我们能将\(x\)\(y\)的子树分别一一配对,由其转移即可。设一个辅助dp数组\(g_{i,s}\)表示\(x\)的儿子匹配到第\(i\)个,\(y\)的儿子匹配的状态为\(s\)的最小代价。转移可以枚举子集。显然\(f_{x,y}=g_{son_x,2^{son_y+1}-1}(son_x=son_y)\)

同时\(g\)的初值可以由子树之间的\(f\)值得到。

时间复杂度不好分析,这里给出一个粗略的上界。容易发现当树的结构是完全\(10\)叉树的时候达到上界。此时深度最大为2.8,数字大约是\(2^{10} \cdot (2.8*10^{2.8})=3.19607*10^9\),但是跑不满,很多无用状态,并且我们要相信pzr神仙!!!

最后要注意,找到重心以后\(siz\),\(son\)要重新算。因为这个wa了一发。

#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define M 13
#define W 1030
#define N 710
#define INF 0x3f3f3f3f
#define ff(i, a, b) for(int i = (a); i < (b); ++i)
#define fo(i, a, b) for(int i = (a); i <= (b); ++i)
#define fd(i, a, b) for(int i = (a); i >= (b); --i)
using namespace std;
inline int read() // negative , long long
{
	int x = 0; char ch = getchar();
	while(ch < ‘0‘ || ch > ‘9‘)	ch = getchar();
	while(ch >= ‘0‘ && ch <= ‘9‘)	x = (x << 3) + (x << 1) + (ch ^ 48), ch = getchar();
	return x;
}
int n, p2[M] = {1}, cnt[W], siz[N], fa[N], son[N], c[3], f[N][N], g[M][W];
int last[N], pre[N << 1], to[N << 1];
bool op1[N], op2[N];
inline void add(int u, int v){static int tot = 0; pre[++tot] = last[u], to[tot] = v, last[u] = tot;}
void findC(int u)
{
	siz[u] = 1, son[u] = 0;
	bool ok = 1;
	for(int i = last[u]; i; i = pre[i])
	{
		int v = to[i];
		if(v == fa[u])	continue ;
		fa[v] = u, findC(v), ++son[u], siz[u] += siz[v];
		if(siz[v] > (n >> 1))	ok = 0;
	}
	if(ok && n - siz[u] <= (n >> 1))	c[++c[0]] = u;
}
void delEdge(int u, int v)
{
	if(to[last[u]] ^ v)
		for(int i = last[u]; pre[i]; i = pre[i])
			if(to[pre[i]] == v && (pre[i] = pre[pre[i]]))	return ;
	last[u] = pre[last[u]];
}
void dfs(int x, int y)
{
	f[x][y] = INF;
	vector<int> sx, sy;
	int full = p2[son[y]] - 1, s = son[x];
	if(s != son[y] || siz[x] != siz[y])	return ;
	for(int i = last[x]; i; i = pre[i])
		if(to[i] ^ fa[x])	sx.push_back(to[i]);
	for(int i = last[y]; i; i = pre[i])
		if(to[i] ^ fa[y])	sy.push_back(to[i]);
	fo(i, 1, s)	fo(j, 1, s)
		dfs(sx[i - 1], sy[j - 1]);
	fo(i, 1, s)	memset(g[i], 0x3f, sizeof(int) * (full + 3));
	fo(i, 1, s)	fo(j, 1, s)	fo(S, p2[j - 1], full)
		if(cnt[S] == i && (S & p2[j - 1]))
			g[i][S] = min(g[i][S], g[i - 1][S ^ p2[j - 1]] + f[sx[i - 1]][sy[j - 1]]);
	f[x][y] = min(f[x][y], g[s][full] + (op1[x] != op2[y]));
}
int main()
{
	n = read();
	fo(i, 1, 10)	p2[i] = p2[i - 1] << 1;
	fo(i, 1, p2[10])	cnt[i] = cnt[i >> 1] + (i & 1);
	int u, v, rt;
	fo(i, 2, n)	u = read(), v = read(), add(u, v), add(v, u);
	fo(i, 1, n)	op1[i] = read();
	fo(i, 1, n)	op2[i] = read();
	findC(1);
	if(c[0] ^ 1)
	{
		rt = ++n; add(n, c[1]), add(n, c[2]), add(c[1], n), add(c[2], n);
		delEdge(c[1], c[2]), delEdge(c[2], c[1]);
	}
	else rt = c[1];
	fa[rt] = 0, findC(rt);
	dfs(rt, rt);
	printf("%d\n", f[rt][rt]);
	return 0;
}


gmoj 3296 LuoguP3296 [SDOI2013]刺客信条

上一篇:『心善渊』Selenium3.0基础 — 18、使用Selenium操作浏览器的弹窗


下一篇:c# – 在用户生成的文件名中使用GUID是个好主意吗?