[日常训练] 树上的游戏

【问题描述】

jmy在大家的帮助下终于成功破解了咒语。但是他很不服气,于是又准备和lkf玩游戏,想要赢回来。
由于之前一直在找最小生成树,jmy想到了一个树上的游戏:给定一棵树,lkf先在这棵树上加一条边(可能出现重边),jmy需要删掉其中的一条边,如果剩下的还是一棵树则jmy赢,否则lkf赢。
问题是,现在jmy不知道lkf会加哪一条边,也不知道自己应该删掉哪一条边。于是现在jmy臆测出了许多种可能,作为jmy的好朋友,你要告诉他:如果lkf在编号为x的点和编号为y的点之间加一条边,jmy删掉第z条边(边按照输入的顺序编号,不包括新加的边)是否能赢。

【输入格式】

第一行一个正整数n,表示节点个数。
接下来n-1行,每行两个正整数x,y,表示原来树上存在一条连接编号为x的节点和编号为y的节点的边。
第n+1行一个正整数Q,表示询问次数。
接下来Q行,每行三个正整数x,y,z,表示一个询问(含义如题所示)。

【输出格式】

输出共Q行。
对于每一个询问,如果lkf会赢就输出YES,否则输出NO。

【输入输出样例】

tree.in
5
1 2
2 3
2 4
4 5
3
2 5 3
2 3 1
1 5 2

tree.out
NO
YES
YES

【数据规模】

对于20%的数据保证n,Q≤1000。
对于另外20%的数据保证n,Q≤10000且树为随机生成。
对于70%的数据保证n,Q≤200000。
对于100%的数据保证n≤200000,Q≤2000000。

【分析】 DFS序

  • 删除一条边后原图显然变成了两棵树,则我们只要考虑在两棵树上分别选一点相连就是一种合法方案
  • 则问题被转化为删除边 uv ,则点若添加边的两点 x,y 不同时在 u v所对应的树中,新的图仍为一棵树。
  • 我们可以用 DFS 序( DFS 遍历树的顺序)来判断,先预处理出点 x DFS序为 num[x] ,以 x 为根的子树大小为sze[x],则若点 x 在点u的子树中,必然要满足 num[u]num[x]num[u]+sze[u]1 。也就是说,若 x,y 都为 u 的子树或都不为u的子树,则新的图不为树
  • 但这样还有一个问题,这里的 u 要满足遍历的深度dep[u]<dep[v],因为 v(dep[v]>dep[u]) 的子树同时也包括 u 的子树,如果按照v来计算,显然是有问题的

【代码】

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;
const int N = 2e5 + 5, M = N << 1;

int lst[N], nxt[M], to[M], fr[M];
int num[N], sze[N], dep[N];
int T = 1, E, n, Q;

inline void addEdge(const int &x, const int &y)
{
    nxt[++T] = lst[x]; lst[x] = T; to[T] = y; fr[T] = x;
    nxt[++T] = lst[y]; lst[y] = T; to[T] = x; fr[T] = y;
}

inline void Dfs(const int &x, const int &fa)
{
    num[x] = ++E; sze[x] = 1; 
    dep[x] = dep[fa] + 1;
    for (int i = lst[x]; i; i = nxt[i])
    {
        int y = to[i];
        if (y == fa) continue;
        Dfs(y, x); sze[x] += sze[y];
    }
}

inline bool Query(const int &x, const int &y)
{
    return (num[y] >= num[x] && num[y] <= num[x] + sze[x] - 1);
}

inline void Swap(int &x, int &y) {int t = x; x = y; y = t;}

inline int get()
{
    char ch; int res;
    while ((ch = getchar()) < '0' || ch > '9');
    res = ch - '0';
    while ((ch = getchar()) >= '0' && ch <= '9')
     res = (res << 3) + (res << 1) + ch - '0';
    return res;
}

int main()
{
    freopen("tree.in", "r", stdin);
    freopen("tree.out", "w", stdout); 
    n = get();
    for (int i = 1; i < n; ++i) addEdge(get(), get());
    Dfs(1, 0); Q = get(); int x, y, z, u, v;
    while (Q--)
    {
        x = get(); y = get(); z = get();
        u = fr[z << 1]; v = to[z << 1];
        if (dep[u] < dep[v]) Swap(u, v);
        puts((Query(u, x) == Query(u, y)) ? "YES":"NO");
    }
    fclose(stdin); fclose(stdout);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值