题目描述
nodgd家里种了一棵树,有一天nodgd比较无聊,就把这棵树画在了一张纸上。另一天nodgd更无聊,就又画了一张。
这时nodgd发现,两次画的顺序是不一样的,这就导致了原本的某一个节点u0在第一幅图中编号为u1,在第二副图中编号为u2。
于是,nodgd决定检查一下他画出的两棵树到底是不是一样的。nodgd已经给每棵树的节点都从1到n进行了编号,即每棵树有n个节点。
如果存在一个1到n的排列p1p2…pn,对于第一幅图中的任意一条边(u,v),在第二幅图中都能找到一条边(pu,pv),则认为这两幅图中的树是一样的。
n<=100000.
分析
这道题正解是优化树的括号表示法,达到均摊分析下O(nlogn) 的水平,然而哈希这么难卡,那就打哈希吧~
哈希的本质是给特定的信息一个特征值,使得信息的判断时间成本下降,那么我们首先得会怎么暴力查看两棵树是否相同。
当然不是用n!这么蠢的方法了…我们定第一颗树1为根,枚举另一棵树的根,然后递归判断是否同构。具体地,对于第一颗树的x,假如他匹配了另一棵树的y,那么再对于x的每个儿子找y匹配的儿子,假如找不到就不合法。这样是n^3的。
考虑抽象一下,我们可以把一棵树用括号序表示出来,子树的根可以看做是两端的左右括号,儿子可以看作是一段括号序,有多个儿子的时候,我们可以规定一定顺序来组合比如字典序和大小,那么假如两棵树同构,他们的括号序一定是一样的,这就是所谓的最小表示法。然而括号序的大小为O(n),我们为了省事,可以直接上哈希。
我们考虑一个点x为根的子树信息有哪些:子树大小siz,x的儿子数cnt,x的儿子的信息。假设我们已经用特征值,即哈希值表示出了x儿子的信息,那么我们只用考虑怎么合并儿子信息,怎么把siz[x]和cnt[x]融入进去。
像上面括号序规定顺序一样,我们把儿子按照siz排序,siz相同按特征值排序。最简单地,我们可以把他们像字符串哈希一样,hash[x]加一个儿子乘一个数再加第二个儿子…siz和cnt也这样搞进去。
为了尽量减少错误率,我们要搞一些额外的东西。引入三个降低错误的因子:二次函数,儿子顺序信息,xor。具体地,一个儿子对hash[x]的贡献是
(sumsiz2son+t∗sumsizson)+hash[son]
,sumsiz指的是排了序之后的儿子子树大小前缀和,t是随便设置的一个质数。然后每个儿子的贡献和hash[x]是xor而不是加法,这样能大大降低出错率。
我们只要定第一棵树1为根,找到第二棵树的合法的根,即哈希值相等的点,再造解即可。然而找根需要哈希的换根,这个要额外打点东西。
为了方便换根操作,我并没有什么xor,儿子顺序信息·,二次函数….直接加法,连加一个乘一次都没有,直接加,十分暴力…但是为了保证一定的正确性,还是弄了两个随机数表ref,ref2,每次把siz和cnt搞进哈希值里我加入的值是ref[cnt],ref2[siz],当然啦…儿子信息加起来之后还是要乘一个数的,另外,我siz和cnt分开维护。
然而…其实没有必要换根…我们只需要随便取第一颗树的一个重心(如果有两个),和第二颗树两个重心都用哈希来check一下…对的输出即可。巨简单的套路…
代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<set>
using namespace std;
typedef long long ll;
typedef double db;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
#define cmax(a,b) (a=(a>b)?a:b)
#define cmin(a,b) (a=(a<b)?a:b)
multiset<int> tr;
const int N=2e5+5;
const ll s1=131,s2=13531,mo=1e9+7;
ll P[N],n,i,pd[N],cnt[2][N],siz[2][N],ref[N],ref2[N],A[2][N],x,y,pp;
ll H1[2][N],H2[2][N],Hr[2][N],upH1[2][N],upH2[2][N];
ll t[2],fst[2][N],nxt[2][N],b[2][N],kan;
ll ran(ll x)
{
return ++kan;
return (1ll*rand()*rand()*rand()+1ll*rand())%x;
}
void cr(ll x,ll y,ll z)
{
t[z]++;
b[z][t[z]]=y;
nxt[z][t[z]]=fst[z][x];
fst[z][x]=t[z];
}
void dfs(ll x,ll y,ll z)
{
ll p;
H1[z][x]=H2[z][x]=0;
siz[z][x]=1;
cnt[z][x]=0;
for(p=fst[z][x];p;p=nxt[z][p])
if (b[z][p]!=y)
{
dfs(b[z][p],x,z);
siz[z][x]+=siz[z][b[z][p]];
cnt[z][x]++;
H1[z][x]+=H1[z][b[z][p]];
H2[z][x]+=H2[z][b[z][p]];
}
H1[z][x]=(H1[z][x]*s1+ref[cnt[z][x]])%mo;
H2[z][x]=(H2[z][x]*s2+ref2[siz[z][x]])%mo;
}
bool cmp(ll a,ll b)
{
return Hr[i][a]<Hr[i][b];
}
void thr(ll ax,ll ay,ll bx,ll by)
{
P[ax]=bx;
ll p,z,ct,x,y;
fo(z,0,1)
{
ct=0;
x=ax;if (z) x=bx;
y=ay;if (z) y=by;
for(p=fst[z][x];p;p=nxt[z][p])
if (b[z][p]!=y)
A[z][++ct]=b[z][p];
i=z;sort(A[z]+1,A[z]+1+ct,cmp);
}
fo(i,1,ct) P[A[0][i]]=A[1][i];
for(p=fst[0][ax];p;p=nxt[0][p])
if (b[0][p]!=ay)
{
z=b[0][p];
thr(z,ax,P[z],bx);
}
}
void swit(ll x,ll y,ll z)
{
ll h1=upH1[z][x],h2=upH2[z][x],tp1,tp2;
ll p,ax;
for(p=fst[z][x];p;p=nxt[z][p])
if (b[z][p]!=y)
{
ax=b[z][p];
h1+=H1[z][ax];
h2+=H2[z][ax];
}
for(p=fst[z][x];p;p=nxt[z][p])
if (b[z][p]!=y)
{
ax=b[z][p];
upH1[z][ax]=(((h1-H1[z][ax])*s1+ref[cnt[z][x]-(x==1)])%mo+mo)%mo;
upH2[z][ax]=(((h2-H2[z][ax])*s2+ref2[n-siz[z][ax]])%mo+mo)%mo;
tp1=((H1[z][ax]-ref[cnt[z][ax]]+upH1[z][ax]*s1+ref[cnt[z][ax]+1])%mo+mo)%mo;
tp2=((H2[z][ax]-ref2[siz[z][ax]]+upH2[z][ax]*s2+ref2[n])%mo+mo)%mo;
Hr[z][ax]=tp1+tp2*mo;
}
for(p=fst[z][x];p;p=nxt[z][p])
if (b[z][p]!=y)
swit(b[z][p],x,z);
}
int main()
{
scanf("%lld",&n);
fo(i,1,n-1)
{
scanf("%lld %lld",&x,&y);
cr(x,y,0);
cr(y,x,0);
}
fo(i,1,n-1)
{
scanf("%lld %lld",&x,&y);
cr(x,y,1);
cr(y,x,1);
}
srand(x+y);
fo(i,0,n)
{
do
{
x=ran(mo-5)+3;
}while (tr.find(x)!=tr.end());
ref[i]=x;
tr.insert(x);
}
fo(i,0,n)
{
do
{
x=ran(mo-5)+3;
}while (tr.find(x)!=tr.end());
ref2[i]=x;
tr.insert(x);
}
dfs(1,0,0);
dfs(1,0,1);
Hr[0][1]=H1[0][1]+H2[0][1]*mo;
Hr[1][1]=H1[1][1]+H2[1][1]*mo;
swit(1,0,0);
swit(1,0,1);
fo(i,1,n) printf("%lld\n",Hr[0][i]);
fo(i,1,n) A[0][i]=A[1][i]=i;
fo(i,0,1) sort(A[i]+1,A[i]+1+n,cmp);
pp=1;
fo(i,1,n)
{
if (Hr[0][A[0][i]]!=Hr[1][A[1][i]])
{
pp=0;
break;
}
}
if (!pp) printf("NO\n");
else
{
printf("YES\n");
thr(A[0][1],0,A[1][1],0);
fo(i,1,n) printf("%lld ",P[i]);
}
}