• JZOJ3054 祖孙询问 题解


    JZOJ3054 祖孙询问 题解


    Description
    已知一棵n个节点的有根树。有m个询问。每个询问给出了一对节点的编号x和y,询问x与y的祖孙关系。

    Input
    输入第一行包括一个整数n表示节点个数。
    接下来n行每行一对整数对a和b表示a和b之间有连边。如果b是-1,那么a就是树的根。
    第n+2行是一个整数m表示询问个数。
    接下来m行,每行两个正整数x和y。

    Output
    对于每一个询问,输出1:如果x是y的祖先,输出2:如果y是x的祖先,否则输出0。

    Sample Input
    10
    234 -1
    12 234
    13 234
    14 234
    15 234
    16 234
    17 234
    18 234
    19 234
    233 19
    5
    234 233
    233 12
    233 13
    233 15
    233 19

    Sample Output
    1
    0
    0
    0
    2

    Hint
    对于100%的.n,m≤40000,每个节点的编号都不超过40000。

    典型的一眼题。
    两个解法:
    1.各种LCA算法。如果u是v的祖先那么LCA(u, v) == u,反之LCA(u, v) == v说明v是u的祖先,如果LCA(u, v)既不等于u也不等于v,那么两个点没有关系。
    解法1代码:

    #include <cmath>
    #include <cstdio>
    #include <cstring>
    #include <cstdlib>
    
    const int N = 4e4 + 3;
    
    int root, n, m, u, v, tot = 0;
    int st[N], to[N << 1], nx[N << 1], f[N][21], dep[N], buc[N];
    
    inline int read()
    {
    	int x = 0, f = 0;
    	char c = getchar();
    	for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = 1;
    	for (; c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) + (c ^ '0');
    	return f ? -x : x;
    }
    inline void add(int u, int v) { to[++tot] = v, nx[tot] = st[u], st[u] = tot; }
    inline void swap(int &a, int &b) { int t = a; a = b, b = t; }
    
    void dfs(int u)
    {
    	for (int i = st[u]; i; i = nx[i])
    	{
    		int v = to[i];
    		if (v != f[u][0])
    			f[v][0] = u, dep[v] = dep[u] + 1, dfs(v);
    	}
    }
    
    int LCA(int u, int v)
    {
    	if (dep[v] > dep[u]) swap(u, v);
    	for (int i = 20; i >= 0; i--)
    		if (dep[f[u][i]] >= dep[v])
    			u = f[u][i];
    	if (u == v) return u;
    	for (int i = 20; i >= 0; i--)
    		if (f[u][i] != f[v][i])
    			u = f[u][i], v = f[v][i];
    	return f[u][0];
    }
    
    void init()
    {
    	n = read();
    	for (int i = 1; i <= n; i++)
    	{
    		u = read(), v = read();
    		if (v == -1) root = u; //记录根节点
    		else
    		{
    			add(u, v), add(v, u);
    			buc[u] = buc[v] = 1;
    		}
    	}
    	f[root][0] = 0, dep[root] = 1, dfs(root);
    	//采用倍增LCA
    	for (int j = 1; j <= 20; j++)
    		for (int i = 1; i <= 40000; i++)
    			if (buc[i]) f[i][j] = f[f[i][j - 1]][j - 1];
    }
    
    void solve()
    {
    	m = read();
    	while (m--)
    	{
    		u = read(), v = read();
    		if (u == v || !buc[u] || !buc[v]) printf("0
    "); //这个判断貌似是多余的
    		else
    		{
    			int lca = LCA(u, v); //求出LCA
    			if (lca == u) printf("1
    ");
    			else if (lca == v) printf("2
    ");
    			else printf("0
    ");
    		}
    	}
    }
    
    int main()
    {
    	init();
    	solve();
    	return 0;
    }
    

    2.dfs序。记录每个节点的dfs序和size,u的子树范围(dfsn[u]表示u的dfs序)是dfsn[u]~dfsn[u] + size[u] - 1,如果dfsn[v]在这个范围内,那么u是v的祖先,反之亦然。
    解法2代码:

    #include <cmath>
    #include <cstdio>
    #include <cstring>
    #include <cstdlib>
    
    const int N = 4e4 + 3;
    
    int n, u, v, q, root, tot = 0, cnt = 0;
    int st[N], to[N << 1], nx[N << 1], tid[N], siz[N];
    
    inline int read()
    {
    	int x = 0, f = 0;
    	char c = getchar();
    	for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = 1;
    	for (; c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) + (c ^ '0');
    	return f ? -x : x;
    }
    inline void add(int u, int v) { to[++tot] = v, nx[tot] = st[u], st[u] = tot; }
    
    void dfs(int u, int from)
    {
    	tid[u] = ++cnt, siz[u] = 1; //记录dfs序和size
    	for (int i = st[u]; i; i = nx[i])
    	{
    		int v = to[i];
    		if (v != from)
    		{
    			dfs(v, u);
    			siz[u] += siz[v]; 累加子树的size
    		}
    	}
    }
    
    void init()
    {
    	n = read();
    	for (int i = 1; i <= n; i++)
    	{
    		u = read(), v = read();
    		if (v == -1) root = u;
    		else add(u, v), add(v, u);
    	}
    	dfs(root, 0);
    }
    
    void solve()
    {
    	q = read();
    	while (q--)
    	{
    		u = read(), v = read();
    		if (tid[u] <= tid[v] && tid[v] <= tid[u] + siz[u] - 1) printf("1
    "); //u是v的祖先
    		else if (tid[v] <= tid[u] && tid[u] <= tid[v] + siz[v] - 1) printf("2
    "); //v是u的祖先
    		else printf("0
    "); //都不是
    	}
    }
    
    int main()
    {
    	init();
    	solve();
    	return 0;
    }
    

    终于有那么一道可以做的题目了。

  • 相关阅读:
    23种设计模式之单例模式
    Java基础之IO技术(一)
    spark利用yarn提交任务报:YARN application has exited unexpectedly with state UNDEFINED
    linux配置了dns后导致mysql远程连接慢问题
    发布网站后localhost可以访问ip不行
    electron编译sqlite3
    vscode文件树缩进
    js判断浏览器类型
    vue可复用slide动画
    vscode使用formate格式化less遇到的坑
  • 原文地址:https://www.cnblogs.com/zjlcnblogs/p/8551119.html
Copyright © 2020-2023  润新知