题解:P13266 [GCJ 2014 Finals] Symmetric Trees

更差的阅读体验


NOIP2025 RP++ 喵。


考虑哈希。

假设 \(c_i\) 为节点 \(i\) 的颜色。

首先为了避免冲突,我们将 \(c_i\) 映射成随机大整数。然后我们构造一个哈希函数,既能体现出树的形态,又能体现点的颜色。可以这样:

\[\operatorname{hash}(u) = c_u + \sum_{v \isin \operatorname{son}_u} \operatorname{shift}(\operatorname{hash}(v)) \]

\(\operatorname{shift}\) 是一些异或位移操作,也可以认为是一种映射。

接下来无非就两种情况:对称中心是点,对称中心是边。

  • 如果对称中心是点,那么这个点 \(u\) 的子树一定可以两两配对,直到剩下零个或一个或两个子树。
    • 我们想到一个看起来很聪明的方法:记录一个点所有子树哈希值的 \(\operatorname{xor}\)
      • 如果最后剩下 \(0\) 个子树,那么异或值为 \(0\),相当好判断。
      • 如果最后剩下 \(1\) 个子树,那么异或值就是剩下的一个子树的哈希值。需要满足这个子树本身是对称的。在实现中可以开一个类似 map 的数据结构来存。
      • 如果最后剩下 \(2\) 个子树,看起来不太好处理。但是仔细想一下,如果真的剩下两个对称的子树,我们可以把对称中心沿着其中一棵子树往下跳,直到跳到分岔的地方。这个时候就变成了剩下 \(1\) 个子树的情况。所以不需要特别处理这种情况。
  • 如果对称中心是边,那很简单,只要看边两端的哈希值是否相等就可以了。

至此可以做到 \(O(n^2)\):枚举对称中心,每次重新计算哈希值。

但是还不够。

考虑换根 dp。我们考虑 \(f_u\) 表示钦定 \(1\) 为根,\(u\) 子树的哈希值,\(g_u\) 表示钦定 \(u\) 为根,整棵树的哈希值。

\(f\) 是好求的。假设 \(fa_u\) 表示 \(u\) 的父亲,那么我们把 \(g\)\(fa_u\) 转移到 \(u\),需要在继承之前 \(f_u\) 的信息的基础上,加入 \(fa_u\) 所在子树的信息。

由于我们之前是求和计算哈希值,所以父亲的哈希值是很好算的,直接拿 \(g_{fa_u}\) 减去 \(f_u\) 即可。

但是上面的讨论中,我们还要把所有对称的子树抠出来扔到 map 里面,所以我们还要同时记录一棵子树是否合法。判断一个大子树抠掉一个小子树是否合法也是容易的。

然后就变成基础的换根了。我们可以全局维护一下每个点邻居的异或值,还有每个点周围对称子树的 map。我在实现中其实用了 unordered_map,这样可以认为是线性的。

这道题就做完了,复杂度 \(O(n)\)

#include<bits/stdc++.h>
#define endl '\n'
#define N 10006
using namespace std;
using ull=unsigned long long;
mt19937_64 rnd(time(0));
struct Node{ull has; int o;} f[N],g[N];
ull refer[206],a[N],xor_sum[N];
int n,ok; char ch[3];
vector<int> G[N];
unordered_map<ull,int> mp[N];
inline ull shift(ull x)
{
	x^=x<<13,x^=x>>17,x^=x<<5;
	return x;
}
int check(int u,ull cut)
{
	ull new_xor=xor_sum[u]^cut;
	int new_cnt=mp[u][new_xor]-(new_xor==cut);
	if(!new_xor||new_cnt)return 1;
	return 0;
}
void dfs1(int u,int fa)
{
	f[u].has=a[u];
	for(int v:G[u])if(v!=fa)
		dfs1(v,u),f[u].has+=shift(f[v].has);
	unordered_map<ull,int> mp; ull xor_sum=0;
	for(int v:G[u])if(v!=fa&&f[v].o)mp[f[v].has]++;
	for(int v:G[u])if(v!=fa)xor_sum^=f[v].has;
	if(!xor_sum||mp[xor_sum])f[u].o=1;
	else f[u].o=0;
}
void dfs2(int u,int fa)
{
	if(fa)
	{
		Node hf; hf.has=g[fa].has-shift(f[u].has);
		if(hf.has==f[u].has)ok=1;
		hf.o=check(fa,f[u].has),g[u].has=a[u];
		for(int v:G[u])
		{
			Node now=(v==fa?hf:f[v]); g[u].has+=shift(now.has);
			if(now.o)mp[u][now.has]++;
			xor_sum[u]^=now.has;
		}
		g[u].o=check(u,0);
		if(g[u].o)ok=2;
	} else for(int v:G[u]) {
		if(f[v].o)mp[u][f[v].has]++;
		xor_sum[u]^=f[v].has;
	}
	for(int v:G[u])if(v!=fa)dfs2(v,u);
}
void solve(int _)
{
	scanf("%d",&n),ok=0; if(_==17)cerr<<n<<endl;
	for(int i='A';i<='Z';i++)refer[i]=rnd();
	for(int i=1;i<=n;i++)
		scanf("%s",ch+1),a[i]=refer[ch[1]],G[i].clear(),mp[i].clear(),xor_sum[i]=0;
	for(int i=1,u,v;i<n;i++)
		scanf("%d%d",&u,&v),G[u].push_back(v),G[v].push_back(u);
	dfs1(1,0),g[1]=f[1],dfs2(1,0);
	printf("Case #%d: ",_);
	printf(ok?"SYMMETRIC\n":"NOT SYMMETRIC\n");
}
main()
{
	int T,_=0; scanf("%d",&T);
	while(T--)solve(++_);
	return 0;
}
posted @ 2025-11-27 21:44  dyc2022  阅读(4)  评论(0)    收藏  举报
/* 设置动态特效 */ /* 设置文章评论功能 */ 返回顶端 levels of contents