实验内容
神秘国度的爱情故事
[问题描述]
某个太空神秘国度中有很多美丽的小村,从太空中可以想见,小村间有路相连,更精确一点说,任意两村之间有且仅有一条路径。小村 A 中有位年轻人爱上了自己村里的美丽姑娘。每天早晨,姑娘都会去小村 B 里的面包房工作,傍晚 6 点回到家。年轻人终于决定要向姑娘表白,他打算在小村 C 等着姑娘路过的时候把爱慕说出来。问题是,他不能确定小村 C 是否在小村 B 到小村 A 之间的路径上。你可以帮他解决这个问题吗?
[基本要求]
1.输入由若干组测试数据组成。每组数据的第 1 行包含一正整数 N ( l 《 N 《 50000 ) , 代表神秘国度中小村的个数,每个小村即从0到 N - l 编号。接下来有 N -1 行输入,每行包含一条双向道路的两个端点小村的编号,中间用空格分开。之后一行包含一正整数 M ( l 《 M 《 500000 ) ,代表着该组测试问题的个数。接下来 M 行,每行给出 A 、 B 、 C 三个小村的编号,中间用空格分开。当 N 为 O 时,表示全部测试结束,不要对该数据做任何处理。
2.对每一组测试给定的 A 、 B 、C,在一行里输出答案,即:如果 C 在 A 和 B 之间的路径上,输出 Yes ,否则输出 No.
原理
(1)链式前向星法存储图和遍历方法
(2)图的深度优先遍历方法
(3)图转化成树,分类讨论点a,点b和点c三者在树上的关系
(4)最近公共祖:先两个点的公共祖先里面,离根最远的那个节点
(5)树链剖分优化树上最近公共祖先LCA(Lowest Common Ancestor)
思路1:
暴力深搜,判断从点a开始找到点c,后判断从点c找到点b,第一次遍历到某个点时都给这个点一个vis标记,通过判断vis标记可以避免重复遍历相同的点
在这里插入图片描述
#pragma GCC optimize(2)
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cstdlib>
using namespace std;
#define AC 0
#define Please return
#define endl "\n"
#define ll long long
const int MAX = 1e5 + 7;
const int mod = 1e9 + 7;
const ll inf = 0x3f3f3f3f;
const double pi = 3.1415926535;
//任意两村之间有且仅有一条路径,说明该图可以用树存储而且不存在重边和环
struct Edge {
int to;
int next;
}edge[MAX << 1];
int head[MAX + 5], cnt;//头,链式前向星存图,点head[x]表示点x所在边在edge数组出现最后一次的下标
void edge_add(int u, int v) { //添加由点u指向点v的边
edge[++cnt].to = v;
edge[cnt].next = head[u];
head[u] = cnt;
}
int n; // 点数为n、边数为n-1
int m; // 询问一组a,b,c的次数
int a, b, c; // 问题是,不能确定小村 C 是否在小村 B 到小村 A 之间的路径上。
bool flag = false, vis[MAX]; //为每次查询初始化一个判断答案成立的flag、vis数组避免每次重复查询同个点
void dfs(int x, bool findc) { //x表示点前节点,findc表示是否已找点c
if (flag) //当找到了点c后又找了点b后返回递归
return;
if (x == b && findc == true) { //判断当前节点是否为点b和之前是否已经找到了点c
flag = true;
return;
}
vis[x] = true;
for (int i = head[x]; i != 0; i = edge[i].next) { //通过链式前向星存图方法对图进行遍历
if (vis[edge[i].to] == true)
continue;
if (findc == true) //当前节点为c时改变findc
dfs(edge[i].to, true);
else {
if (x == c) //找到点c后,从点c开始往外遍历还未遍历过的节点
dfs(edge[i].to, true);
else
dfs(edge[i].to, false);
}
}
return;
}
int main() {
ios_base::sync_with_stdio(0); cin.tie(0), cout.tie(0);
cin >> n;
//输出n-1条边
for (int i = 1, u, v; i <= n - 1; i++) {
cin >> u >> v;
edge_add(u, v);
edge_add(v, u); //无向图正反建边
}
cin >> m;
for (int i = 1; i <= m; i++) {
cin >> a >> b >> c;
for (int j = 0; j <= n - 1; j++)
vis[j] = false;
flag = false;
if (a == c || b == c) //特判点c是是否与点a或点b重合
cout << "Yes" << endl;
else {
dfs(a, 0);
if (flag)
cout << "Yes" << endl;
else
cout << "No" << endl;
}
}
Please AC;
}
思路2
树链剖分优化树上最近公共祖先LCA
对图转化成树,选定0为根节点,先后两次dfs预处理所需的树上性质,再根据深度deep大小和链顶top元素不同进行交换跳转,直到该两点在同一条链上。
#pragma GCC optimize(2)
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cstdlib>
using namespace std;
#define endl '\n'
const int MAX = 500000 + 1;
//任意两村之间有且仅有一条路径,说明该图可以用树存储而且不存在重边和环
struct Edge{
int next; //上一个节点在head数组的cnt下标
int to; //指向的下一个节点
}edge[MAX << 1];
int head[MAX], cnt;//头,链式前向星存图,点head[x]表示点x所在边在edge数组出现最后一次的下标
void edge_add(int u, int v) { 添加由点u指向点v的边
edge[++cnt].to = v;
edge[cnt].next = head[u];
head[u] = cnt;
}
int n; // 点数为n、边数为n-1
int m; // 询问一组a,b,c的次数
int father[MAX];//表示点x的父节点
int deep[MAX];//表示点x的深度
int siz[MAX];//表示点x的子树大小,子树的节点个数
int hson[MAX];//表示点x的重子节点,重子节点表示其子节点中子树节点个数最大的子结点
int top[MAX];//表示点x所在重链的顶部节点,重链表示重子节点所在的链
//第一次进行dfs预处理各个点x的父节点father、深度deep、子树大小siz和当前节点下最符合的重节点hson
void dfs1(int now) { //当前节点为now,它的父节点为fa,深度为dep
siz[now] = 1; //子树大小
deep[now] = deep[father[now]]+1; //深度+1
for (int i = head[now]; i != 0; i = edge[i].next) {
int to = edge[i].to; //当前节点i所在边指向的下一个节点
if (to==father[now])continue; //因为是双向建边,所以需要跳过下一个节点是父节点的情况
father[to] = now; //父节点
dfs1(to); //换根再进行深度遍历
siz[now] += siz[to]; //统计以now为根节点的子树大小
if (hson[now]==0||siz[hson[now]] <siz[to] ){//选择出以节点now为父节点的子树中最大子子树
hson[now] = to;
}
}
}
//第二次进行dfs预处理所在链的链顶top,应初始化为结点本身
void dfs2(int now,int top_fa) {
top[now] = top_fa;//这个点所在链的顶端
if (hson[now])//
dfs2(hson[now], top_fa);//按先处理重儿子,再处理轻儿子的顺序递归处理
for (int i = head[now]; i != 0; i = edge[i].next) {
int to = edge[i].to;
if (to == father[now] || to == hson[now])continue;
dfs2(to, to); //对于每一个轻儿子都有一条从它自己开始的链
}
}
//求最近公共祖先¶
int lca(int u, int v) {
//不断向上跳重链,先跳所在重链顶端深度较大的那个,当跳到同一条重链上时,深度较小的结点即为 LCA
while (top[u] != top[v]) {
if (deep[top[u]] > deep[top[v]])
u = father[top[u]];
else
v = father[top[v]];
}
return deep[u] < deep[v] ? u : v;
}
int main() {
ios_base::sync_with_stdio(0); cout.tie(0); cin.tie(0);
cin >> n; // 输入点数
for (int i = 0; i < n - 1; i++) {
int u, v; cin >> u >> v; //输入点u,v代表u-v之间构成边
edge_add(u, v); //正反双向建边
edge_add(v, u);
}
dfs1(0);
dfs2(0, 0);
cin >> m; // 输出查询组数
for (int i = 0; i < m; i++) {
int a, b, c; cin >> a >> b >> c; //每组都数组a,b,c三个点
int d = lca(a, b), ac = lca(a, c), bc = lca(b, c); //找到点a与点b、点a与点c和点b与点c在树上的最近公共祖先
if (ac == c && bc == c) { //如果ac==c并且bc==c,说明c结点是a和b结点的公共祖先
if (c == d) //如果c==d,说明c就是a和b的最近公共祖先,c必定在a和b的路径上
cout << "Yes" << endl;
else
cout << "No" << endl; //如果c!=d,说明c不是a和b的最近公共祖先,a和b的路径上不包括c
}
else if (ac == c || bc == c) //c是a的祖先或者是b的祖先,说明c在a到d的路径上或者在b到d的路径上
cout << "Yes" << endl; //此时c一定是a和b路径上的点
else
cout << "No" << endl; //如果c不是a的祖先,也不是b的祖先,则a和b的路径上不会经过c点
}
return 0;
}
ACM-GZHU-Online-Judeg-1602
哈哈,需要内网才可以进呀!如果没猜错的话,你应该是校友的拉OvO,建议老师可以用OJ来测试结果而来验证代码正确性,当然暴力肯定不是可以过的啦 !最后,欢迎各位新同学积极参与GZHU-ACM