【JSOI2016】独特的树叶

1 篇文章 0 订阅
1 篇文章 0 订阅

题目大意

有一棵树a,树b是在a的基础上多加一个叶子,且打乱了编号顺序,现在要求b上多出的那个节点是哪个(可能有多个)

暴力

枚举一个节点作为根节点,然后用各种奇怪的依据(例如儿子个数,节点度数,最大子树大小等)来匹配,逐个逐个下去找,大概时间复杂度O(有点玄学)

正解

树的hash

因为是匹配,所以我们可以想到hash,那么对于树的hash我们可以怎样做呢?
很直观的可以用最小表示法,例如求一棵子树的hash,设树根为x,其儿子为a[1..n],以i节点为根的子树的hash值为v[i],那么将其儿子的hash值从小到大排序,弄个素数hash一下,注意最好加上一个依据,不然可能挂掉,时间复杂度是O(n log n)

思路

对于a的每个节点,求出其子树hash值,然后求出作为树根的hash值和其向父亲方向的树的hash值,将每个点作为树根的hash值扔进一个set里,枚举b的一个叶子,用类似的方法求出删掉它之后的树的hash值

code

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<set>

#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)

using namespace std;

typedef long long LL;

const int N = 100010;
const int mo = 1e+9+9;
const int pri = 12589;

LL tim[N],q[N];
int k;
struct node{
    int x;
    LL v;
}u[N];
bool cmp(node x,node y){
    return x.v<y.v;
}

struct tree{
    struct edge{
        int x,next;
    }e[N*2];
    int tot,n;
    int h[N],r[N],fa[N],s[N];
    LL v[N],rt[N],f[N];
    LL suf[N],pre[N];

    void inse(int x,int y){
        e[++tot].x=y;
        e[tot].next=h[x];
        h[x]=tot;
    }

    void init(){
        tot=0;
        fo(i,1,n)h[i]=r[i]=v[i]=f[i]=fa[i]=rt[i]=0;
        fo(i,1,n-1){
            int x,y;
            scanf("%d%d",&x,&y);
            inse(x,y);
            inse(y,x);
            r[x]++;
            r[y]++;
        }
    }

    void dfs1(int x){
        s[x]=1;
        for(int p=h[x];p;p=e[p].next)
            if (e[p].x!=fa[x]){
                fa[e[p].x]=x;
                dfs1(e[p].x);
                s[x]+=s[e[p].x];
            }
        k=0;
        for(int p=h[x];p;p=e[p].next)
            if (fa[e[p].x]==x)q[++k]=v[e[p].x];
        sort(q+1,q+1+k);
        v[x]=0;
        fo(i,1,k)v[x]=(v[x]*pri%mo+q[i])%mo;
        v[x]=(v[x]*pri%mo+s[x])%mo;
    }

    void dfs2(int x){
        k=0;
        if (x>1)u[++k].v=f[x],u[k].x=fa[x];
        for(int p=h[x];p;p=e[p].next)
            if (fa[e[p].x]==x){
                u[++k].x=e[p].x;
                u[k].v=v[e[p].x];
            }
        std::sort(u+1,u+1+k,cmp);
        fo(i,1,k)pre[i]=(pre[i-1]*pri%mo+u[i].v)%mo;
        suf[k+1]=0;
        fd(i,k,1)suf[i]=(suf[i+1]+u[i].v*tim[k-i]%mo)%mo;
        fo(i,1,k)
        if (u[i].x!=fa[x]){
            f[u[i].x]=(pre[i-1]*tim[k-i]%mo+suf[i+1])%mo;
            f[u[i].x]=(f[u[i].x]*pri%mo+n-s[u[i].x])%mo;
        }
        for(int p=h[x];p;p=e[p].next)
            if (fa[e[p].x]==x)dfs2(e[p].x);
    }

    void calc(){
        dfs1(1);
        dfs2(1);
        fo(i,1,n){
            k=0;
            for(int p=h[i];p;p=e[p].next)
                if (fa[e[p].x]==i)q[++k]=v[e[p].x];
            if (fa[i])q[++k]=f[i];
            sort(q+1,q+1+k);
            rt[i]=0;
            fo(j,1,k)rt[i]=(rt[i]*pri%mo+q[j])%mo;
            rt[i]=(rt[i]*pri%mo+n)%mo;
        }
    }
}a,b;
set<LL> s;
int m;

int main(){
    freopen("leaf.in","r",stdin);
    freopen("leaf.out","w",stdout);
    tim[0]=1;
    s.clear();
    scanf("%d",&m);
    fo(i,1,m+2)tim[i]=tim[i-1]*pri%mo;
    a.n=m;
    a.init();
    b.n=m+1;
    b.init();
    a.calc();
    b.calc();
    fo(i,1,m)s.insert(a.rt[i]);
    fo(i,1,m+1)
    if (b.r[i]==1&&((i!=1&&s.find(b.f[i])!=s.end())||(i==1&&s.find(b.v[b.e[b.h[i]].x])!=s.end()))){
        printf("%d\n",i);
        break;
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}

一开始用的模数太小挂掉了啊!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值