[JZOJ5454]仔细的检查

题目描述

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+tsumsizson+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]);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值