更差的阅读体验
NOIP2025 RP++ 喵。
考虑哈希。
假设 \(c_i\) 为节点 \(i\) 的颜色。
首先为了避免冲突,我们将 \(c_i\) 映射成随机大整数。然后我们构造一个哈希函数,既能体现出树的形态,又能体现点的颜色。可以这样:
\(\operatorname{shift}\) 是一些异或位移操作,也可以认为是一种映射。
接下来无非就两种情况:对称中心是点,对称中心是边。
- 如果对称中心是点,那么这个点 \(u\) 的子树一定可以两两配对,直到剩下零个或一个或两个子树。
- 我们想到一个看起来很聪明的方法:记录一个点所有子树哈希值的 \(\operatorname{xor}\)。
- 如果最后剩下 \(0\) 个子树,那么异或值为 \(0\),相当好判断。
- 如果最后剩下 \(1\) 个子树,那么异或值就是剩下的一个子树的哈希值。需要满足这个子树本身是对称的。在实现中可以开一个类似
map的数据结构来存。 - 如果最后剩下 \(2\) 个子树,看起来不太好处理。但是仔细想一下,如果真的剩下两个对称的子树,我们可以把对称中心沿着其中一棵子树往下跳,直到跳到分岔的地方。这个时候就变成了剩下 \(1\) 个子树的情况。所以不需要特别处理这种情况。
- 我们想到一个看起来很聪明的方法:记录一个点所有子树哈希值的 \(\operatorname{xor}\)。
- 如果对称中心是边,那很简单,只要看边两端的哈希值是否相等就可以了。
至此可以做到 \(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;
}