• 距离咨询[tarjan求lca]


    农夫约翰有N(2<=N<=40000)个农场,标号1到N。M(2<=M<=40000)条的不同的垂直或水平的道路连结着农场,道路的长度不超过1000.这些农场的分布就像下面的地图一样,图中农场用F1..F7表示:

    每个农场最多能在东西南北四个方向连结4个不同的农场。此外,农场只处在道路的两端。道路不会交叉而且每对农场间有且仅有一条路径。邻居鲍伯要约翰来导航,但约翰丢了农场的地图,他只得从电脑的备份中修复率。每一条道路的信息如下:

    从农场23往南经距离10到达农场17
    
    从农场1往东经距离7到达农场17
    
    . . .
    

    最近美国过度肥胖非常普遍。农夫约翰为了让他的奶牛多做运动,举办了奶牛马拉松。马拉松路线要尽量长。

    奶牛们拒绝跑马拉松,因为她们悠闲的生活无法承受约翰选择的如此长的赛道。因此约翰决心找一条更合理的赛道。他打算咨询你。读入地图之后会有K个问题,每个问题包括2个整数,就是约翰感兴趣的2个农场的编号,请尽快算出这2个农场间的距离。

    输入格式

    第1行:两个分开的整数N和M。

    第2到M+1行:每行包括4个分开的内容,F1,F2,L,D分别描述两个农场的编号,道路的长度,F1到F2的方向N,E,S,W。

    第2+M行:一个整数K(1<=K<=10000).

    第3+M到2+M+K行:每行输入2个整数,代表2个农场。

    输出格式

    对每个问题,输出单独的一个整数,给出正确的距离。

    样例

    样例输入

    7 6
    1 6 13 E
    6 3 9 E
    3 5 7 S
    4 1 3 N
    2 4 20 W
    4 7 2 S
    3
    1 6
    1 4
    2 6
    

    样例输出

    13
    3
    36
    

    思路

    注意到每两个农场间只有一条路相连. 那就是一棵树, 至于方向, 完全没有意义,在读入时直接忽略即可

    两个节点间的最短路, 就可以用lca求, 这里我们用tarjan求lca:

    我们需要两个边表, 一个存储原图, 一个存储询问.

    看注释吧

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    const int maxn = 80005;
    int n, m, head[maxn], len=-1, dfn[maxn], tot=-1, headq[maxn], vis[maxn], dis[maxn], f[maxn];
    struct edge{
    	int to, nx, w;
    }e[maxn], q[maxn];
    void add(int x, int y, int w){//建图
    	e[++len].to = y;
    	e[len].nx = head[x];
    	e[len].w = w;
    	head[x] = len;
    	e[++len].to = x;
    	e[len].nx = head[y];
    	e[len].w = w;
    	head[y] = len; 
    }
    void add_que(int u, int v){//存储询问
    	q[++tot].to = v;
    	q[tot].nx = headq[u];
    	headq[u] = tot;
    	q[++tot].to = u;
    	q[tot].nx = headq[v];
    	headq[v] = tot;
    }
    void init(int n){//初始化
    	for(int i=0; i<=n; i++) f[i] = i;//并查集初始化
    	memset(vis,0,sizeof(vis));
        memset(dis,0,sizeof(dis));
        memset(head,-1,sizeof(head));  
        memset(headq,-1,sizeof(headq));
    }
    int find(int x){//并查集求祖先
    	if(x != f[x]) f[x] = find(f[x]);
    	return f[x];
    }
    void tarjan(int root){
    	vis[root] = 1;//标记访问
    	f[root] = root;//初始化父亲
    	for(int i=head[root]; ~i; i=e[i].nx){//遍历子节点
    		int v = e[i].to;//子节点
    		if(!vis[v]){//如果没访问过
    			dis[v] =  dis[root] + e[i].w;//子节点到根节点距离=父节点到根节点距离+边权
    			tarjan(v);//递归子节点
    			f[v] = root;//更新父亲
    		}
    	}
    	for(int i=headq[root]; ~i; i=q[i].nx){//处理以当前根节点为起点的询问
    		int v = q[i].to;//终点
    		if(vis[v]){//如果已经计算过了
    			q[i].w = dis[root] + dis[v] - 2*dis[find(v)];//这次询问的距离=起点到祖先的距离+终点到祖先的距离-2*起点终点lca到祖先的距离
    			q[i^1].w = q[i].w;
    		}
    	}
    }
    int main(){
    	scanf("%d%d", &n, &m);
    	init(n);
    	for(int i=1; i<=m; i++){
    		int f1, f2, l; scanf("%d%d%d%*c%*c", &f1, &f2, &l);//方向是无用的,直接忽略
    		add(f1, f2, l);
    	}
    	int k; scanf("%d", &k);
    	for(int i=1; i<=k; i++){
    		int u, v; scanf("%d%d", &u, &v);
    		add_que(u, v);
    	}
    	tarjan(1);
    	for(int i=0; i<tot; i+=2) printf("%d
    ", q[i].w);
    	return 0;
    }
    

    在这里我解释一下为什么find(v)就可以求出lca
    看这个图.

    假如我们求4 , 7 间的最短路.

    在我们tarjan的时候, 只有根节点的子树都tarjan完时,才会更新他并查集父节点.当我们tarjan(7)时,发现有和7有关的询问,并且4已经访问过, 这时f[3]仍为3, find(3) 就是3, 即lca, 假如4还有子树的话, tarjan(7)时4这边的子树已经访问完, fa[3]已经更新, 所以, 我们可以这样求出lca

  • 相关阅读:
    协方差矩阵
    SLAM中的关键帧是什么?有什么用?如何选择关键帧?
    EKF算法与非线性优化算法的比较
    LC217 存在重复元素
    LC42 接雨水
    LC20 有效的括号
    LC3 无重复最长子串
    LC4 寻找两个有序数组的中位数
    ubuntu16.04下安装g2o
    小米 各版本手机系统包 线刷包 卡刷包
  • 原文地址:https://www.cnblogs.com/hzoi-poozhai/p/12815059.html
Copyright © 2020-2023  润新知