欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

[日常训练] 树上的游戏

程序员文章站 2022-05-20 16:45:53
...

【问题描述】

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不同时在uv所对应的树中,新的图仍为一棵树。
  • 我们可以用DFS序(DFS遍历树的顺序)来判断,先预处理出点xDFS序为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;
}
相关标签: 深度优先遍历