神秘国度的爱情故事-数据结构课设-暴力/树剖LCA

本文探讨了如何利用图的链式前向星存储和深度优先遍历解决一个浪漫的数学问题:在一个每个村庄间仅有一条路径的神秘国度里,判断某村庄是否位于两个特定村庄之间的路径上。通过两种不同的算法思路,即暴力深搜和树链剖分优化的最近公共祖先(LCA)算法,实现了高效判断。这两种方法分别通过遍历图的边和优化树结构来避免重复计算,从而提高了查询效率。
摘要由CSDN通过智能技术生成

实验内容

神秘国度的爱情故事
[问题描述]
某个太空神秘国度中有很多美丽的小村,从太空中可以想见,小村间有路相连,更精确一点说,任意两村之间有且仅有一条路径。小村 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
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值