世界树(worldtree)

这篇博客介绍了如何解决HNOI2014中的世界树问题。通过建立虚树,利用dfs序枚举每个点,并使用栈维护从根到各个点的链,来确定边的信息。通过树形DP求解管辖点和距离,最终解决点对间的最短路径问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【HNOI2014】世界树(worldtree)

(File IO): input:worldtree.in output:worldtree.out
Time Limits: 2000 ms Memory Limits: 524288 KB

Description
Description
Input
Input
Output
Output
Sample Input

10
2 1
3 2
4 3
5 4
6 1
7 3
8 3
9 4
10 1
5
2
6 1
5
2 7 3 6 9
1
8
4
8 7 10 3
5
2 9 3 5 8

Sample Output

1 9
3 1 4 1 1
10
1 1 3 5
4 1 3 1 1

Data Constraint
Data
Hint
Hint_1
Hint_2


Solution

一眼看上去——不会。。。
果断上暴力。。。

其实,观察到m[i]<=3105∑m[i]<=3∗105可以考虑建立虚树。
虚树是什么?
根据需要,保留有用的点,点与点之间的当成一条边,并且要保留数的形状,也就是lca要保留。
lca?
两两的一共12n(n1)12n(n−1)个??但,事实上,不同的lca顶多n-1个。
因为:

  • 对于点对(x,y,z):lca(x,z)一定是lca(x,y)和lca(y,z)中的一个

lca(x,y)=a,lca(y,z)=b,lca(x,z)=clca(x,y)=a,lca(y,z)=b,lca(x,z)=c
a为y的祖先,b为y的祖先,a,b,y在一条路径上
假设deepa<deepbdeepa<deepb,那么a为b的祖先,同时a为y,z的某一公共祖先
a为x,y,z的某一公共祖先
若a并不是最近的祖先,则d|dtree(a)∃d|d∈tree(a)使得d为,x,y,z的公共祖先(tree(x)表示x的子树)
lca(x,y)=min deep of{d|dtree(a)}alca(x,y)=min deep of{d|d∈tree(a)}≠a与题设矛盾
deepa>deepbdeepa>deepb时同理
故 对于点对(x,y,z):lca(x,z)一定是lca(x,y)和lca(y,z)中的一个

于是n-1个lca分别为某n-1对(x,y)的lca
为了避免点凌乱而导致的lca没有全部查找出来,可以将询问按照原树的dfs序排序,加入相邻两点的lca

—————————————————重点从这里开始:—————————————————

因为虚树中的的dfs序与原树相同,那么按照dfs序枚举每一个虚树中的点,用栈维护从根延下来的链,那么边信息就可以知道。

计算答案:

对于一条边(x,y)已知x,y分别被哪个点管辖,设管辖x的点为c[x],管辖y的点为c[y],当c[x]=c[y]c[x]=c[y]则x到y都归c[x]管辖,否则,设c[x]到x的距离为dis[x],c[y]到y的距离为dis[y],x到y的距离为D(x,y)D(x,y),列一个二元一次方程求出x到y从哪里切断,注意c[x]和c[y]的大小关系。
那么切断的位置拥有的点数,在原树中跑一下倍增即可。

怎么求c[x]和dis[x]呢?
做树形DP:往上扫一遍用x更新fa[x],往下扫一遍用fa[x]更新x。
于是问题就快(fu)乐(za)地解决了。

#include<cstring>
#include<cstdio>
#include<algorithm>
#define N 300100

using namespace std;

int n,to[N+N],nex[N+N],fir[N],dfn[N],qry[N],siz[N],fa[N],q,d[N+N],dis[N],cnt[N],head,tail,que[N];
int deep[N],tot,top,f[20][N],T,sta[N],times,is[N],Fir[N],Nex[N+N],To[N+N],in[N],Fa[N];
bool cmp(int a,int b){return dfn[a]<dfn[b];}

void link(int x,int y){
    to[++top]=y;nex[top]=fir[x];fir[x]=top;
}
void Link(int x,int y){
    To[++top]=y;Nex[top]=Fir[x];Fir[x]=top;
}

void dfs(){
    int j,i,x,y,cnt;
    for(head=0,fa[que[tail=1]=1]=1,deep[1]=0;head^tail;)
        for(i=fir[x=que[++head]],siz[x]=1;i;i=nex[i])
            if((y=to[i])!=fa[x])fa[que[++tail]=y]=x,deep[y]=deep[x]+1;
    for(head=tail;head;--head)siz[fa[que[head]]]+=siz[que[head]];
    for(dfn[1]=1,head=1;head<=tail;++head)for(cnt=0,i=fir[x=que[head]];i;i=nex[i])
        if((y=to[i])!=fa[x])dfn[y]=dfn[x]+cnt+1,cnt+=siz[y],f[0][y]=x;
    for(j=1;j<20;j++)for(i=1;i<=n;i++)f[j][i]=f[j-1][f[j-1][i]];
}

int lca(int x,int y){
    if(deep[x]>deep[y])swap(x,y);
    for(int i=19;i>=0;i--)if(deep[f[i][y]]>=deep[x])y=f[i][y];
    if(x==y)return x;
    for(int i=19;i>=0;i--)if(f[i][x]!=f[i][y])x=f[i][x],y=f[i][y];
    return f[0][x];
}

int up(int x,int k){
    for(int i=19;i>=0;i--)if((1<<i)<=k)k-=1<<i,x=f[i][x];return x;
}

int main(){
    freopen("worldtree.in","r",stdin);
    freopen("worldtree.out","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<n;i++){
        int x,y;scanf("%d %d",&x,&y);link(x,y);link(y,x);
    }dfs();deep[0]=-1;
    scanf("%d",&q);
    while(q--){
        int m;scanf("%d",&m);tot=m;times++;
        for(int i=1;i<=m;i++)scanf("%d",&d[i]),cnt[d[i]]=0,is[qry[i]=d[i]]=times;
        sort(d+1,d+m+1,cmp);
        for(int i=1;i<m;i++)d[++tot]=lca(d[i],d[i+1]);
        sort(d+1,d+tot+1,cmp);
        for(int i=1,t=tot;i<=t;i++)if(i==1)tot=1;else if(d[i]!=d[i-1])d[++tot]=d[i];
        for(int i=1;i<=tot;i++)Fir[d[i]]=0,dis[d[i]]=n+n;
        top=0;sta[T=1]=d[1];
        for(int i=2;i<=tot;i++){
            while(T && siz[sta[T]]+dfn[sta[T]]<=dfn[d[i]])--T;
            int x=sta[T],y=d[i];Link(x,y);Link(y,x);Fa[y]=x;sta[++T]=y;
        }
        for(int i=tot;i>=1;i--){
            int x=d[i],o,y=Fa[x];if(is[x]==times)dis[x]=0,in[x]=x;if(i==1)continue;
            if(dis[x]+deep[x]-deep[y]<dis[y] || (dis[x]+deep[x]-deep[y]==dis[y] && in[x]<in[y]))
                dis[y]=dis[x]+deep[x]-deep[y],in[y]=in[x];
        }
        for(int i=2;i<=tot;i++){
            int x=Fa[d[i]],y=d[i];
            if(dis[x]+deep[y]-deep[x]<dis[y] || (dis[x]+deep[y]-deep[x]==dis[y] && in[x]<in[y]))
                dis[y]=dis[x]+deep[y]-deep[x],in[y]=in[x];
        }
        cnt[in[d[1]]]=n;
        for(int i=1;i<top;i+=2){
            int y=To[i],x=To[i+1];
            if(in[x]!=in[y]){
                int k,k2=dis[x]-dis[y]+deep[y]-deep[x];
                if(k2&1)k=k2/2;else k=k2/2-(in[x]<in[y]?1:0);
                int t=up(y,k);cnt[in[x]]-=siz[t];cnt[in[y]]+=siz[t];
            }
        }
        for(int i=1;i<=m;i++)printf("%d ",cnt[qry[i]]);printf("\n");
    }
    fclose(stdin);fclose(stdout);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值