【JZOJ 4587】 Snow的追寻

Description

这里写图片描述
这里写图片描述

Analysis

此题本人跑得最快,rank1了233
求出树的欧拉序。顺便用序弄出rmq快速求lca。
我们知道,一个节点在序中表示的是一段区间。而题目询问的是一些树的直径。
我们可以用线段树维护区间表示的节点内的直径,可是怎么合并?

直径性质

两棵树用一条边合并,新树直径两端一定是原本两棵树直径四个端点中的两个。

具体证明可以看crazy的课件。
所以,按上述方法合并区间,对于两个不能走的子树,就不合并之。
然后这样合并完就是询问的直径啦。

Code

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define efo(i,v) for(int i=last[v];i;i=next[i])
using namespace std;
const int N=100010,M=N*2;
int n,m,tot,to[M],next[M],last[N],dep[N],fir[N],las[N],rmq[M][20];
struct segment
{
    int u,v,l;
}tr[M*4],ans;
struct node
{
    int d,v;
}a[M];
void link(int u,int v)
{
    to[++tot]=v,next[tot]=last[u],last[u]=tot;
}
int lca(int u,int v)
{
    int l=fir[u],r=fir[v];
    if(l>r) swap(l,r);
    int len=log2(r-l+1);
    int x=rmq[l][len],y=rmq[r-(1<<len)+1][len];
    return a[x].d<a[y].d?a[x].v:a[y].v;
}
void dfs(int v,int from,int d)
{
    a[++m].d=d,a[m].v=v,dep[v]=d;
    efo(i,v)
    {
        int u=to[i];
        if(u==from) continue;
        dfs(u,v,d+1);
        a[++m].d=d,a[m].v=v;
    }
}
int dis(int u,int v)
{
    return dep[u]+dep[v]-2*dep[lca(u,v)];
}
void merge(segment &c,segment a,segment b)
{
    c=a.l>b.l?a:b;
    int t=dis(a.u,b.u);
    if(t>c.l) c.l=t,c.u=a.u,c.v=b.u;
    t=dis(a.u,b.v);
    if(t>c.l) c.l=t,c.u=a.u,c.v=b.v;
    t=dis(a.v,b.u);
    if(t>c.l) c.l=t,c.u=a.v,c.v=b.u;
    t=dis(a.v,b.v);
    if(t>c.l) c.l=t,c.u=a.v,c.v=b.v;
}
void build(int v,int l,int r)
{
    if(l==r)
    {
        tr[v].u=tr[v].v=a[l].v,tr[v].l=0;
        return;
    }
    int mid=(l+r)>>1;
    build(v+v,l,mid);
    build(v+v+1,mid+1,r);
    merge(tr[v],tr[v+v],tr[v+v+1]);
}
void find(int v,int l,int r,int x,int y)
{
    if(x>y) return;
    if(l==x && r==y)
    {
        merge(ans,ans,tr[v]);
        return;
    }
    int mid=(l+r)>>1;
    if(y<=mid) find(v+v,l,mid,x,y);
    else
    if(x>mid) find(v+v+1,mid+1,r,x,y);
    else
    find(v+v,l,mid,x,mid),find(v+v+1,mid+1,r,mid+1,y);
}
int main()
{
    freopen("snow.in","r",stdin);
    freopen("snow.out","w",stdout);
    int _,u,v;
    scanf("%d %d",&n,&_);
    fo(i,1,n-1)
    {
        scanf("%d %d",&u,&v);
        link(u,v),link(v,u);
    }
    dfs(1,0,0);
    fo(i,1,m)
    {
        if(!fir[a[i].v]) fir[a[i].v]=i;
        las[a[i].v]=i;
        rmq[i][0]=i;
    }
    fo(j,1,int(log2(m)))
        fo(i,1,m-(1<<j)+1)
        {
            int x=rmq[i][j-1],y=rmq[i+(1<<(j-1))][j-1];
            rmq[i][j]=a[x].d<a[y].d?x:y;
        }
    build(1,1,m);
    while(_--)
    {
        scanf("%d %d",&u,&v);
        ans.u=ans.v=ans.l=0;
        int x1=fir[u],y1=las[u],x2=fir[v],y2=las[v];
        find(1,1,m,1,min(x1,x2)-1);
        find(1,1,m,max(y1,y2)+1,m);
        if(x2>y1) find(1,1,m,y1+1,x2-1);
        if(x1>y2) find(1,1,m,y2+1,x1-1);
        printf("%d\n",ans.l);
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值