codeforces 725G. Messages on a Tree

5 篇文章 0 订阅
4 篇文章 0 订阅

题目链接http://codeforces.com/contest/725/problem/G
题目大意:给一棵有n+1个节点的树,编号0~n,根节点是0,其余每个点 i 的父节点pi都比 i 小。有m个操作,第 i 个操作中,节点xi在ti时刻向根节点发送一个信息,信息沿着树边向父亲传递,根节点收到信息后会立即回答。对于每个节点,当它发出信息(包括传递子节点传来的信息)后,就会处于等待状态直到得到回答。信息在边上转移需要1时刻。如果一条信息传递给某个处于等待状态的点v,那么v会直接传回一个回答。如果多条信息同时传给某个不处于等待状态的v,那么v会选择其中起点编号最小的信息并给其他信息传回一个回答。如果一个节点同时收到回答和信息,那么它会先传给子节点回答,然后接收信息。求出每个操作得到回答的时间。
数据范围:1 ≤ n, m ≤ 200 000, 0 ≤ pi < i, 1 ≤ xi ≤ n, 1 ≤ ti ≤ 10^9

题解:话说这次的G题好像没有什么难点/坑点啊(那个调了半个小时程序的人是谁啊喂)。

首先我们可以先把操作排一下序,使得当前操作只受之前的操作的影响。假设点 i 的深度是dep[i],那么我们可以以dep[xi]+ti为第一关键字,xi为第二关键字排序。这样如果xi和xj(i < j)的路径冲突,那么xi一定是先到达的。

之后我们只需要按顺序进行操作,找出每个操作走到哪个点会得到回答。
假设第 i 个操作可以走到的终点是end[i],那么我们应该对xi到end[i]的路径上的点求出它们得到回答的时间getans[i],对于end[i],getans[end[i]]=ti+dep[xi]-dep[end[i]]。

对于树上路径的处理我们可以采用树链剖分+线段树。

假设剖分完后每个点的新标号为dfn[i]。对于同一条重链上的点,假设y是这条链在xi到end[i]路径上的最底部的点,我们可以知道getans[y]=dep[y]-dep[end[i]]+getans[end[i]]。对于这条链上的任意一个点z,getans[z]=getans[y]-(dfn[y]-dfn[z])。这个式子中只有dfn[z]是不确定的,其余都可以算出来。

现在的问题是如何求出end[i]。假设每个点收到询问信息的时间是rec[i],则rec[xi]=ti。对于同一条重链上的点,链的底部节点y的rec[y]=ti+dep[xi]-dep[y],对于链上其他节点z,rec[z]=rec[y]+dfn[y]-dfn[z]。这个式子中还是只有dfn[z]不确定。

我们要找到end[i],就是要找到xi到根的路径中是否有一个点z满足rec[z] < (原来的)getans[z]。即rec[y]+dfn[y] < getans[y]-dfn[y]+2*dfn[z]。因此在给每个点的getans打标记的时候,我们只要标记tag=getans[y]-dfn[y],那么区间上的每个节点u的getans值就是tag+2*u,对于线段树上的区间[l..r],如果它的标记是tag,那么最大值就是tag+2*r,我们可以轻易的求出一个区间的最大值。在询问end[i]时,只要找到路径上离xi最近的满足tag+2*u>rec[y]+dfn[y]的u就是所求的end[i],若不存在这样的u,end[i]=0(root)。接着把xi到end[i]的路径打上标记即可。每个操作的答案ans[i]=ti+2*(dep[xi]-dep[end[i]])。

说几个打题过程中出现的坑点(明明是你自己不认真好吧):
1.打标记的时候不要把end[i]也标记上,因为end[i]可能正在等待其他询问,标记end[i]可能导致getans[end[i]]变小。
2.沿着链往父亲走的时候,不能遇到0就停,因为可能在这条路径中0作为单独一条链,也需要标记。
3.不要忘了线段树的区间位置从0开始不是从1!!!(多少年没犯过的错误了今天居然hhhh)

时间复杂度O(mlog^2n)

代码如下:

#include <algorithm>
#include <cstdio>
const int N=200005,inf=2000000000;
int a[N],t[N],id[N],fa[N],dep[N],num[N],son[N],q[N],dfn[N],
    top[N],mx[N<<2],tag[N<<2],ans[N],n,m,cnt=0,fd;
bool cmp(int x,int y){
    if (t[x]+dep[a[x]]!=t[y]+dep[a[y]])
        return t[x]+dep[a[x]]<t[y]+dep[a[y]];
    return a[x]<a[y];
}
void pushup(int now){
    mx[now]=std::max(mx[now<<1],mx[now<<1^1]);
}
void pushdown(int now,int l,int mid,int r){
    if (tag[now]<-inf) return;
    tag[now<<1]=tag[now<<1^1]=tag[now];
    mx[now<<1]=mid*2+tag[now];
    mx[now<<1^1]=r*2+tag[now];
    tag[now]=-inf-1;
}
void modify(int now,int l,int r,int x,int y,int z){
    if (x<=l && r<=y){
        tag[now]=z;mx[now]=r*2+z;
        return;
    }
    int mid=(l+r)>>1;
    pushdown(now,l,mid,r);
    if (x<=mid) modify(now<<1,l,mid,x,y,z);
    if (y>mid) modify(now<<1^1,mid+1,r,x,y,z);
    pushup(now); 
}
void query(int now,int l,int r,int x,int y,int z){
    if (mx[now]<=z) return;
    if (l==r){fd=q[l];return;}
    int mid=(l+r)>>1;
    pushdown(now,l,mid,r);
    if (fd==-1 && y>mid) query(now<<1^1,mid+1,r,x,y,z);
    if (fd==-1 && x<=mid) query(now<<1,l,mid,x,y,z);
    pushup(now);
}
int work(int x,int t){
    int y=x;
    for (;1;y=fa[top[y]]){
        fd=-1;
        query(1,0,n,dfn[top[y]],dfn[y],t+dfn[y]);
        if (fd>=0){
            t+=dep[y]-dep[fd];y=fd;break;
        }
        if (!y) break;
        t+=dep[y]-dep[fa[top[y]]];
    }
    int z=t+dep[x]-dep[y];
    for (;1;x=fa[top[x]]){
        if (top[x]==top[y]){
            if (x!=y) modify(1,0,n,dfn[y]+1,dfn[x],t-dfn[y]);
            break;
        }
        modify(1,0,n,dfn[top[x]],dfn[x],dep[x]-dep[y]+t-dfn[x]);
    }
    return z;
}
int main(){
    scanf("%d%d\n",&n,&m);
    for (int i=1;i<=n;i++){
        scanf("%d",&fa[i]);
        dep[i]=dep[fa[i]]+1;
        num[i]=1;
    }
    for (int i=n;i;i--){
        if (num[i]>num[son[fa[i]]]) son[fa[i]]=i;
        if (fa[i]) num[fa[i]]+=num[i];
    }
    for (int i=son[0];i;i=son[i]) q[dfn[i]=++cnt]=i;
    for (int i=0;i<=n;i++)
        if (!dfn[i])
            for (int j=i;j;j=son[j])
                top[q[dfn[j]=++cnt]=j]=i;
    for (int i=1;i<=m;i++) scanf("%d%d\n",&a[i],&t[i]);
    for (int i=1;i<=m;i++) id[i]=i;
    std::sort(id+1,id+m+1,cmp);
    mx[1]=tag[1]=-inf;
    for (int i=1;i<=m;i++)
        ans[id[i]]=work(a[id[i]],t[id[i]]);
    for (int i=1;i<=m;i++) printf("%d ",ans[i]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值