[bzoj4928][SDOI省队集训2017]dierti

题目描述

对于一棵有根树,定义一个点u 的k− 子树为u 的子树中距离u 不超过k 的部分。注意,假如u 的子树中不存在距离u 为k 的点,则u 的k−子树是不存在的。
定义两棵子树是相同的,当且仅当不考虑点的标号时,他们的形态是相同的(儿子的顺序也需要考虑)。给定一棵n 个点,点的标号在[1,n],以1 为根的有根树。问最大的k,使得存在两个点u ̸= v,满足u 的k− 子树与v 的k− 子树相同。

二分+hash

我们怎么对树hash?可以对括号序hash!
二分答案,然后按照深度顺序加入点,线段树维护区间hash,就可以啦。
复杂度两个log,贼慢。

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
typedef long long ll;
const int maxn=100000+10,mo1=1000000007,mo2=998244353;
struct dong{
    int x,y;
    friend bool operator <(dong a,dong b){
        return a.x<b.x||a.x==b.x&&a.y<b.y;
    }
} zlt,b[maxn];
int h[maxn],go[maxn],next[maxn],a[maxn],dep[maxn],mx[maxn],two[maxn*2][2];
int h2[maxn*2],g2[maxn],n2[maxn],dl[maxn];
int dfn[maxn],low[maxn],tree[maxn*8][2],size[maxn*8];
int i,j,k,l,r,mid,t,x,y,n,m,top,tot,head,tail,now;
int read(){
    int x=0,f=1;
    char ch=getchar();
    while (ch<'0'||ch>'9'){
        if (ch=='-') f=-1;
        ch=getchar();
    }
    while (ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
void add(int x,int y){
    go[++tot]=y;
    next[tot]=h[x];
    h[x]=tot;
}
void dfs(int x){
    mx[x]=x;
    dfn[x]=++top;
    int t=h[x];
    while (t){
        dep[go[t]]=dep[x]+1;
        dfs(go[t]);
        if (dep[mx[go[t]]]>dep[mx[x]]) mx[x]=mx[go[t]];
        t=next[t];
    }
    low[x]=++top;
}
void add2(int x,int y){
    g2[++tot]=y;
    n2[tot]=h2[x];
    h2[x]=tot;
}
void bfs(){
    head=0;tail=1;
    dl[1]=1;
    while (head<tail){
        now=dl[++head];
        t=h[now];
        while (t){
            dl[++tail]=go[t];
            t=next[t];
        }
    }
    fo(i,1,n) add2(dep[i],i);
}
void clear(int p,int l,int r){
    tree[p][0]=tree[p][1]=size[p]=0;
    if (l==r) return;
    int mid=(l+r)/2;
    clear(p*2,l,mid);clear(p*2+1,mid+1,r);
}
void change(int p,int l,int r,int a,int b,int f){
    if (l==r){
        if (f==1){
            size[p]=1;
            tree[p][0]=tree[p][1]=b;
        }
        else size[p]=tree[p][0]=tree[p][1]=0;
        return;
    }
    int mid=(l+r)/2;
    if (a<=mid) change(p*2,l,mid,a,b,f);else change(p*2+1,mid+1,r,a,b,f);
    tree[p][0]=((ll)tree[p*2][0]*two[size[p*2+1]][0]%mo1+tree[p*2+1][0])%mo1;
    tree[p][1]=((ll)tree[p*2][1]*two[size[p*2+1]][1]%mo2+tree[p*2+1][1])%mo2;
    size[p]=size[p*2]+size[p*2+1];
}
void query(int p,int l,int r,int a,int b){
    if (l==a&&r==b){
        zlt.x=((ll)zlt.x*two[size[p]][0]%mo1+tree[p][0])%mo1;
        zlt.y=((ll)zlt.y*two[size[p]][1]%mo2+tree[p][1])%mo2;
        return;
    }
    int mid=(l+r)/2;
    if (b<=mid) query(p*2,l,mid,a,b);
    else if (a>mid) query(p*2+1,mid+1,r,a,b);
    else{
        query(p*2,l,mid,a,mid);
        query(p*2+1,mid+1,r,mid+1,b);
    }
}
bool check(int ans){
    clear(1,1,2*n);
    top=0;
    fd(i,n,1){
        if (i!=n&&dep[dl[i]]!=dep[dl[i+1]]){
            t=h2[dep[dl[i]]+ans+1];
            while (t){
                y=g2[t];
                change(1,1,n*2,dfn[y],0,-1);
                change(1,1,n*2,low[y],0,-1);
                t=n2[t];
            }
        }
        x=dl[i];
        change(1,1,n*2,dfn[x],1,1);
        change(1,1,n*2,low[x],0,1);
        if (dep[x]+ans>dep[mx[x]]) continue;
        zlt.x=zlt.y=0;
        query(1,1,n*2,dfn[x],low[x]);
        b[++top]=zlt;
    }
    sort(b+1,b+top+1);
    fo(i,1,top-1)
        if (b[i].x==b[i+1].x&&b[i].y==b[i+1].y) return 1;
    return 0;
}
int main(){
    freopen("ernd.in","r",stdin);freopen("ernd.out","w",stdout);
    n=read();
    two[0][0]=two[0][1]=1;
    fo(i,1,2*n){
        two[i][0]=two[i-1][0]*2%mo1;
        two[i][1]=two[i-1][1]*2%mo2;
    }
    fo(i,1,n){
        k=read();
        fo(j,1,k) a[j]=read();
        fd(j,k,1) add(i,a[j]);
    }
    dep[1]=1;
    dfs(1);
    tot=0;
    bfs();
    l=0;r=dep[mx[1]]-1;
    while (l<r){
        mid=(l+r+1)/2;
        if (check(mid)) l=mid;else r=mid-1;
    }
    printf("%d\n",l);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值