【题解】【模板】树同构([BJOI2015]树的同构)
\(\text{Solution:}\)
由于月赛有一个和树同构相关的题目,所以来学一下树同构。
这里不用树哈希的做法,考虑用括号序列。
我们发现:当进入一个点的时候记录一个左括号,出去的时候记录一个右括号,这样会形成一个长度为 \(2n\) 的括号序列。
同时,在没有标号的情况下,这种括号序列是可以确定树的结构的。
但是我们观察到,当我们更改子树的拼接顺序,那么其括号序列就会改变。
所以我们考虑求字典序最小的括号序列。容易证明两棵树同构当且仅当两棵树对应的最小括号序列相同。
于是我们考虑直接暴力求这个东西。先把子树的最小表示求出来,然后暴力排序。复杂度 \(O(n^2)\) 因为排序的复杂度与 string 拼接的复杂度相比并不高。
然后考虑无根树怎么比较。考虑转化为有根树,这样就可以直接用重心来做了,由于最多两个重心,这样就可以只比较两个点的最小表示。复杂度就是 \(O(n^2\times m)\) 的了。
代码里面封装了一个对树求最小表示的板子。
#include<bits/stdc++.h>
using namespace std;
typedef double db;
//#define int long long
const int mod=1e9+7;
const db eps=1e-10;
inline int Max(int x,int y){return x>y?x:y;}
inline int Min(int x,int y){return x<y?x:y;}
inline db Max(db x,db y){return x-y>eps?x:y;}
inline db Min(db x,db y){return x-y<eps?x:y;}
inline int Add(int x,int y,int M=mod){return (x+y)%M;}
inline int Mul(int x,int y,int M=mod){return 1ll*x*y%M;}
inline int Dec(int x,int y,int M=mod){return (x-y+M)%M;}
inline int Abs(int x){return x<0?-x:x;}
inline int read(){
int s=0,w=1;
char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch)){s=s*10+ch-'0';ch=getchar();}
return s*w;
}
inline void write(int x){
if(x<0)putchar('-'),x=-x;
if(x>9)write(x/10);
putchar(x%10+'0');
}
inline int qpow(int x,int y){
int res=1;
while(y){
if(y&1)res=Mul(res,x);
x=Mul(x,x);y>>=1;
}
return res;
}
typedef pair<int,int> pr;
#define fi first
#define se second
#define mk make_pair
#define pb emplace_back
#define poly vector<int>
#define Bt(a) bitset<a>
const int N=200;
struct Tree_Construction{
int head[N],tot,n,siz[N],mx[N],Mi;
struct E{int nxt,to;}e[N];
string f[N],g[N],tmp;
inline void link(int x,int y){
e[++tot]=(E){head[x],y};
head[x]=tot;
}
void dfs(int x,int fa){
siz[x]=1;
for(int i=head[x];i;i=e[i].nxt){
int j=e[i].to;
if(j==fa)continue;
dfs(j,x);
siz[x]+=siz[j];
mx[x]=Max(mx[x],siz[j]);
}
mx[x]=Max(mx[x],n-siz[x]);
Mi=Min(Mi,mx[x]);
}
void Show(int x,int fa){
f[x]="0";
for(int i=head[x];i;i=e[i].nxt){
int j=e[i].to;
if(j==fa)continue;
Show(j,x);
}
int cnt=0;
for(int i=head[x];i;i=e[i].nxt){
int j=e[i].to;
if(j==fa)continue;
g[++cnt]=f[j];
}
sort(g+1,g+cnt+1);
for(int i=1;i<=cnt;++i)f[x]+=g[i];
f[x]+="1";
}
void Init(){
n=read();
for(int i=1;i<=n;++i){
int u=read();
if(!u)continue;
link(i,u);link(u,i);
}
}
string Get(){
Init();
Mi=n+1;
dfs(1,0);
tmp="1";
for(int i=1;i<=n;++i){
if(mx[i]==Mi){
Show(i,0);
tmp=min(tmp,f[i]);
}
}
return tmp;
}
}tr[N];
int T;
string mn[N];
int main(){
T=read();
for(int i=1;i<=T;++i){
mn[i]=tr[i].Get();
for(int j=1;j<=i;++j){
if(mn[j]==mn[i]){
printf("%d\n",j);
break;
}
}
}
return 0;
}
树哈希:
考虑一种树哈希的方式,比最小表示法的复杂度要低。也可以直接用基数排序降低复杂度。
考虑对一棵有根树哈希如何来做。给出下列公式:
设 \(p_i\) 表示第 \(i\) 个质数,\(siz[i]\) 表示以 \(i\) 为根的子树的大小,\(f[x]\) 表示以 \(x\) 为根的子树的哈希值。那么:
这样就可以直接用 \(O(n)\) 处理出一棵有根树的哈希值了。
考虑如何求无根树,用换根 \(dp.\) 设 \(g[u]\) 为以 \(u\) 为根的全树的哈希值,那么:
这样就可以处理好无根树的情况了。
本题直接用了 set
维护所有树的哈希值。其实也可以直接求重心做两遍 \(dp\) 啥的,但还是换根方便一些。
注意要预处理好前 \(n\) 个质数。
#include<bits/stdc++.h>
using namespace std;
typedef double db;
//#define int long long
const int mod=1e9+7;
const db eps=1e-14;
inline int Max(int x,int y){return x>y?x:y;}
inline int Min(int x,int y){return x<y?x:y;}
inline db Max(db x,db y){return x-y>eps?x:y;}
inline db Min(db x,db y){return x-y<eps?x:y;}
inline int Add(int x,int y,int M=mod){return (x+y)%M;}
inline int Mul(int x,int y,int M=mod){return 1ll*x*y%M;}
inline int Dec(int x,int y,int M=mod){return (x-y+M)%M;}
inline int Abs(int x){return x<0?-x:x;}
inline int read(){
int s=0,w=1;
char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch)){s=s*10+ch-'0';ch=getchar();}
return s*w;
}
inline void write(int x){
if(x<0)putchar('-'),x=-x;
if(x>9)write(x/10);
putchar(x%10+'0');
}
inline int qpow(int x,int y){
int res=1;
while(y){
if(y&1)res=Mul(res,x);
x=Mul(x,x);y>>=1;
}
return res;
}
typedef pair<int,int> pr;
#define fi first
#define se second
#define mk make_pair
#define pb emplace_back
#define poly vector<int>
const int N=500;
const int SN=40000;
namespace Refined_heart{
typedef unsigned long long ull;
int p[SN],cnt,vis[SN];
ull f[N][N],g[N][N];
void Euler(){
for(int i=2;i<SN;++i){
if(!vis[i])p[++cnt]=i;
for(int j=1;j<=cnt&&i*p[j]<SN;++j){
vis[i*p[j]]=1;
if(i%p[j]==0)break;
}
}
}
poly G[N][N];
inline void link(int x,int y,int pos){G[pos][x].pb(y);}
int T,n,siz[N][N];
void dfs1(int x,int fa,int pos){
siz[pos][x]=1;f[pos][x]=1;
for(auto j:G[pos][x]){
if(j==fa)continue;
dfs1(j,x,pos);
siz[pos][x]+=siz[pos][j];
f[pos][x]+=1ull*f[pos][j]*p[siz[pos][j]];
}
}
void dfs2(int x,int fa,int pos){
g[pos][x]=(g[pos][fa]-f[pos][x]*p[siz[pos][x]])*p[siz[pos][1]-siz[pos][x]]+f[pos][x];
for(auto v:G[pos][x]){
if(v==fa)continue;
dfs2(v,x,pos);
}
}
void sol(int pos){
n=read();
for(int i=1;i<=n;++i){
int u=read();
if(!u)continue;
link(u,i,pos);link(i,u,pos);
}
dfs1(1,0,pos);
dfs2(1,0,pos);
}
void solve(){
Euler();
T=read();
for(int i=1;i<=T;++i)sol(i);
for(int i=1;i<=T;++i){
set<ull>s;
s.clear();
for(int j=1;j<=siz[i][1];++j)s.insert(g[i][j]);
for(int j=1,k=0;j<=T;++j){
for(int l=1;l<=siz[j][1];++l){
if(siz[i][1]==siz[j][1]&&s.find(g[j][l])!=s.end()){
k=1;
printf("%d\n",j);
break;
}
}
if(k)break;
}
}
}
}
int main(){
Refined_heart::solve();
return 0;
}