【BNUOJ】Borrow Classroom

第十五届北京师范大学程序设计竞赛决赛

B. Borrow Classroom

每年的BNU校赛都会有两次赛前培训,为此就需要去借教室,由于SK同学忙于出题,这个事情就由小Q同学来跑腿。SK同学准备从宿舍出发,把借教室的单子交给小Q同学让他拿去教务处盖章,但是何老师突然发现SK同学好像借错教室了,想抢在借教室的单子被送到教务处之前拦截下来。

现在把校园抽象成一棵n个节点的树,每条边的长度都是一个单位长度,从1到n编号,其中教务处位于1号节点,接下来有q个询问,每次询问中SK同学会从B号节点出发,到C号节点找到小Q同学并将借教室的单子交给他,然后小Q同学再从C号节点出发前往教务处,何老师会从A号节点出发开始拦截。

所有人在一个单位时间内最多走一个单位距离,只要何老师在单子还没被送到教务处之前遇到拿着单子的同学都算拦截成功,如果小Q同学已经到了教务处,那么由于小Q同学手速极快,单子会被立即交上去,即使何老师到了教务处也无济于事,你需要判断何老师是否能够拦截成功。

Input

第一行是一个正整数 T(5) ,表示测试数据的组数,

对于每组测试数据,

第一行是两个整数 n,q(1n,q100000) ,分别表示节点数和询问数,

接下来n-1行,每行包含两个整数 x,y(1x,yn) ,表示x和y之间有一条边相连,保证这些边能构成一棵树,

接下来q行,每行包含三个整数 A,B,C(1A,B,Cn) ,表示一个询问,其中A是何老师所在位置,B是SK同学所在位置,C是小Q同学所在位置,保证小Q同学初始不在教务处。

Output

对于每个询问,输出一行,如果何老师能成功拦截则输出”YES”(不含引号),否则输出”NO”(不含引号)。

Sample Input

1
7 2
1 2
2 3
3 4
4 7
1 5
1 6
3 5 6
7 5 6

Sample Output

YES
NO

题解

首先要支持求树上两点的 LCA 和一条到跟的路径上的到是第二个点的位置。
判断是否可行,我们只需要判断当在 bc1 路径上走,走到倒数第二个点(1的孩子)的时候 a 点走到了哪里,如果a可以更早的走到 bc1 的倒数第二个点,那就是可以拦截的,否则拦截不了。

代码

#include <bits/stdc++.h>

using namespace std;

const int maxn = 200010;

int T, n, m;
int head[maxn], nxt[maxn<<1], to[maxn<<1], cnt;
int dep[maxn], top[maxn], sz[maxn], son[maxn], st[maxn], tp;
int q[maxn], l, r, fa[maxn];
bool vis[maxn];
void add(int a, int b){nxt[++ cnt] = head[a], to[head[a] = cnt] = b;}
void pre(){
    l = 1, r = 0, tp = 0;
    q[++ r] = 1, vis[1] = 1, dep[1] = 0;
    while(l <= r){
        int x = q[l ++]; sz[x] = 1;
        for(int i = head[x]; i; i = nxt[i]){
            int u = to[i]; if(vis[u] || fa[x] == u) continue;
            vis[u] = 1, dep[u] = dep[x] + 1, fa[u] = x;
            q[++ r] = u;
        }
    }
    for(int i = n; i >= 1; i --){
        int x = q[i]; if(fa[x] == 0) continue;
        sz[fa[x]] += sz[x];
        if(sz[son[fa[x]]] < sz[x]) son[fa[x]] = x;
    }
    st[++ tp] = 1, top[1] = 1;
    while(tp){
        int x = st[tp --];
        for(int i = head[x]; i; i = nxt[i]){
            int u = to[i]; if(u == son[x] || u == fa[x]) continue;
            st[++ tp] = u, top[u] = u;
        }
        if(son[x]) st[++ tp] = son[x], top[son[x]] = top[x];
    }
}

int lca(int x, int y){
    while(top[x] != top[y]){
        if(dep[top[x]] < dep[top[y]]) swap(x, y);
        x = fa[top[x]];
    }
    return dep[x] < dep[y] ? x : y;
}

int find(int x){
    if(x == 1)
        return -1;
    while(true){
        if(top[x] == 1) return son[1];
        if(dep[top[x]] == 1) return top[x];
        x = fa[top[x]];
    }
}

int main(){
    scanf("%d", &T);
    while(T --){
        scanf("%d%d", &n, &m), cnt = 0;
        for(int i = 1; i <= n; i ++) head[i] = 0, son[i] = 0, vis[i] = 0;
        for(int i = 1, a, b; i < n; i ++){
            scanf("%d%d", &a, &b);
            add(a, b), add(b, a);
        }
        pre();
        for(int i = 1, a, b, c; i <= m; i ++){
            scanf("%d%d%d", &a, &b, &c);
            if(b == 1 && c == 1 && (puts("NO"),true)) continue;
            int w = dep[b] + 2 * dep[c] - 2 * dep[lca(b,c)];
            if(dep[a] < w) puts("YES");
            else if(dep[a] + (find(a) == (c == 1 ? find(b) : find(c)) ? -1 : 1) <= w - 1) puts("YES");
            else puts("NO");
        }
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值