题目大意:
就是现在给出一个由N个结点的树, 2 <= N <= 100000, 结点编号为1~N, 接下来是Q次询问, Q <= 100000, 每次询问给出X, Y 代表询问当以X为树的根的时候, 结点Y的儿子中最小的编号是多少, 以及Y的子孙中最小的编号是多少, 如果此时Y没有任何儿子或子孙, 输出no answer
大致思路:
第一次写用LCA的题......
不过学了下LCA之后感觉还是挺简单的一道题
首先以1为根处理出此时这棵树上每个节点的儿子节点中最小的和次小的儿子以及子孙(最小的和次小的必须来自不同的子树), 直接树形dp即可, 时间复杂度O(n)
然后对于每次询问X, Y不难发现
只有当X是Y的子孙时(以1为根), Y在这个情况下的答案才和以1为根时处理出来的不一样, 即当LCA(X, Y) != Y时直接输出之前找出的结果即可
而当X是Y的子孙时, 如果Y != 1那么1一定会变成Y的子孙, 也就是说Y的子孙当中最小的一定是1, 而最小的儿子就需要判断原本Y的最小儿子是不是X的祖先, 同LCA判断一下即可, 如果原本的最小儿子来自X所在的子树, 取次小的儿子和Y的父亲取最小值即为此时的最小儿子, 否则去最小儿子和Y的父亲中的最小值即为最小儿子
当Y == 1时, Y原本是没有父亲的, 那么只需要判断原本Y的最小儿子的来源是不是X所在子树以及Y的最小子孙的来源是不是X所在子树, 若是则取其次即可
然后LCA方面的话用的是ST形式的方法, 查询很快, 也就没有什么大问题了, 就是一个简单的树形DP题了
代码如下:
Result : Accepted Memory : 26400 KB Time : 936 ms
/* * Author: Gatevin * Created Time: 2015/7/23 14:35:15 * File Name: B.cpp */ #pragma comment(linker, "/STACK:1024000000,1024000000") #include<iostream> #include<sstream> #include<fstream> #include<vector> #include<list> #include<deque> #include<queue> #include<stack> #include<map> #include<set> #include<bitset> #include<algorithm> #include<cstdio> #include<cstdlib> #include<cstring> #include<cctype> #include<cmath> #include<ctime> #include<iomanip> using namespace std; const double eps(1e-8); typedef long long lint; #define maxn 100010 vector<int> G[maxn]; int n; int fa[maxn];//父亲结点 int dfn[maxn];//深度 int st[2*maxn][20];//st表 int pos[maxn];//pos[v]表示点v在st[][0]中最后出现的位置 int sum;//st表中元素个数 int son[maxn][2];//son[u][0]表示结点u儿子中标号最小的, son[u][1]表示次小的, 没有为1e9 int grandson[maxn][2];//gandson[u][0]表示结点u孙子中标号最小的, grandson[u][1]表示次小的, 没有则为1e9 int sonfrom[maxn];//记录son[u][0]的答案来自结点sonfrom[u] int grandsonfrom[maxn];// 记录grandson[u][0]的答案来自u的儿子中grandsonfrom[u]的那部分子树 void add(int x) { st[++sum][0] = x; pos[x] = sum; return; } void dfs(int now)//以1为根 { son[now][0] = son[now][1] = grandson[now][0] = grandson[now][1] = 1e9; sonfrom[now] = grandsonfrom[now] = -1; for(int i = 0, sz = G[now].size(); i < sz; i++) { int u = G[now][i]; if(u == fa[now]) continue; fa[u] = now; add(now); dfn[u] = dfn[now] + 1; if(u < son[now][0]) { son[now][1] = son[now][0]; son[now][0] = u; sonfrom[now] = u; } else if(u < son[now][1]) son[now][1] = u; dfs(u); u = min(u, grandson[u][0]); if(u < grandson[now][0]) { grandson[now][1] = grandson[now][0]; grandson[now][0] = u; grandsonfrom[now] = u; } else if(u < grandson[now][1]) grandson[now][1] = u; } add(now); return; } int Min(int x, int y) { return dfn[x] < dfn[y] ? x : y; } void initLCA() { for(int j = 1; (1 << j) <= sum; j++) for(int i = 1; i + (1 << j) - 1 <= sum; i++) st[i][j] = Min(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]); return; } int lca(int u, int v)//询问结点u和v的LCA { u = pos[u], v = pos[v]; if(u > v) swap(u, v); int k = 0; while((1 << (k + 1)) <= v - u + 1) k++; return Min(st[u][k], st[v - (1 << k) + 1][k]); } void solve(int x, int y)//以X为根, 求Y的儿子和子孙的最小标号 { int pa = lca(x, y); if(pa != y)//此时y的子孙不变 { if(son[y][0] == 1e9) puts("no answers!"); else printf("%d %d\n", son[y][0], grandson[y][0]); return; } //否则X是Y的子孙, 以X为根时, 树发生的旋转改变了Y的子孙 //由于原本是以1为根进行的树形DP, 当Y不是根节点(1)时, 经过旋转1一定是Y的子孙, 儿子则还要判断y的父亲 if(y != 1)//y不是根节点1, y一定有父亲 { if(lca(x, sonfrom[y]) == y)//最大儿子不来自x所在子树 printf("%d 1\n", min(fa[y], son[y][0])); else printf("%d 1\n", min(fa[y], son[y][1])); return; } //否则Y == 1为根节点, 则根节点Y的子树中不包含X的子树上的点旋转为Y的子孙 int mson, mgrandson; if(lca(x, sonfrom[y]) == y)//说明最小儿子不在X所在部分子树 mson = son[y][0]; else mson = son[y][1]; if(lca(x, grandsonfrom[y]) == y)//说明最小子孙不在X所在部分子树 mgrandson = grandson[y][0]; else mgrandson = grandson[y][1]; if(mson == 1e9)//说明没有儿子了 puts("no answers!"); else printf("%d %d\n", mson, mgrandson); return; } int main() { int T; int Q; scanf("%d", &T); for(int cas = 1; cas <= T; cas++) { scanf("%d %d", &n, &Q); for(int i = 1; i <= n; i++) G[i].clear(); int u, v; for(int i = 1; i < n; i++) { scanf("%d %d", &u, &v); G[u].push_back(v); G[v].push_back(u); } fa[1] = dfn[1] = sum = 0; dfs(1); initLCA(); while(Q--) { int x, y; scanf("%d %d", &x, &y); solve(x, y); } putchar('\n'); } return 0; }