洛谷 P5651 基础最短路练习题



基础最短路练习题

题目链接

https://www.luogu.com.cn/problem/P5651

题目背景

YSGH 牛逼

题目描述

给定 n n n 个点 m m m 条边的简单无向连通图 G G G,边有边权。保证没有重边和自环。

定义一条简单路径的权值为路径上所有边边权的异或和。

保证 G G G 中不存在简单环使得边权异或和不为 0 0 0

Q Q Q 次询问 x x x y y y 的最短简单路径。

输入格式

第一行三个正整数 n , m , Q n, m, Q n,m,Q

接下来 m m m 行,一行三个非负整数 x , y , v x, y, v x,y,v 1 ≤ x , y ≤ n 1 \le x, y \le n 1x,yn),表示一条连接 x , y x, y x,y,权值为 v v v 的无向边。保证没有重边和自环。

接下来 Q Q Q 行,一行两个正整数 x , y x, y x,y 1 ≤ x , y ≤ n 1 \le x, y \le n 1x,yn),表示一次询问。

输出格式

Q Q Q 行,一行一个整数表示答案。

样例 #1

样例输入 #1

3 2 1
1 2 2
2 3 3
1 3

样例输出 #1

1

提示

数据点编号 n , Q ≤ n, Q \le n,Q特殊性质
1 , 2 1,2 1,2 10 10 10
3 , 4 3,4 3,4 20 20 20
5 , 6 5,6 5,6 10 5 {10}^5 105 m = n − 1 m = n - 1 m=n1
7 , 8 7,8 7,8 10 5 {10}^5 105 v ≤ 1 v \le 1 v1
9 , 10 9,10 9,10 10 5 {10}^5 105

对于 100 % 100\% 100% 的数据,满足 1 ≤ n ≤ 10 5 1 \le n \le {10}^5 1n105 1 ≤ m ≤ 2 n 1 \le m \le 2n 1m2n 0 ≤ v < 2 30 0 \le v < 2^{30} 0v<230



题意解析

本题就是让我们求两点之间异或值最小的那一条路。


思路分析

  • 题解中很多写了关于环的问题,但是我觉得与环无关,路径中有环无环跟最后的答案无关,因为是根据异或值选路,而不是根据路径长度。
  • 本题看到最后有 Q Q Q 个询问,下意识认为应该用 F l o y d Floyd Floyd,但是很明显 O ( n 3 ) O(n^3) O(n3) 的复杂度绝对会 T L E TLE TLE,而所有最短路中只有堆优化 D i j k s t r a Dijkstra Dijkstra O ( m l o g n ) O(mlogn) O(mlogn) 能过,但是单源最短路算法如何过一道多元最短路算法题呢?
  • 异或(xor)的性质
    • 对于任何数 a a a,都有 a ⊕ a = 0 a ⊕ a = 0 aa=0
    • 对于任何数 a a a,都有 a ⊕ 0 = a a ⊕ 0 = a a0=a
    • 这意味着,一个数与自己进行异或运算会得到0,一个数与0进行异或运算还是它自己。
    • 由上述性质可得:
      • 异或运算的逆运算其实就是异或运算本身。
      • 如果你有一个数 a a a,并且你知道 a a a 与另一个数 b b b 的异或结果 c c c(即 c = a ⊕ b c = a ⊕ b c=ab),那么你可以通过再次进行异或运算来找出 a a a(即 a = c ⊕ b a = c ⊕ b a=cb)。
  • 所以我们由异或性质可以得到本题的结局方案:
    • 1 1 1 为源点找所有点的单源最短路,询问为 x , y x, y x,y 没有关系,我们将两者答案进行异或操作即可。
      • dist[x]:表示 1 − > x 1 -> x 1>x 的异或值。
      • dist[y]:表示 1 − > x − > y 1 -> x -> y 1>x>y 的异或值。
      • dist[x] ^ dist[y]:由于 1 − > x 1 -> x 1>x 异或上自己为 0 0 0,而 0 0 0 异或上任何数都是那个数本身,所以就剩下了 x − > y x -> y x>y 的异或值。
  • 那么最终的公式就是: a n s = d i s t [ x ]     ˆ   d i s t [ y ] ans = dist[x]\ \^\ \ dist[y] ans=dist[x]  ˆ dist[y]

CODE

#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
#include <queue>
#define ll long long
#define INF 0x3f3f3f3f 

using namespace std;

typedef pair<int, int> pii;  // 定义一个类型,表示一对整数

const int N = 1E5 + 10, M = 4E5 + 10;
int n, m, q;  // n是点的数量,m是边的数量,q是询问的数量
int h[N], e[M], ne[M], idx;  // h, e, ne用于存储图的信息,idx是当前边的编号
ll w[M];  // w用于存储每条边的权值
ll dist[N];  // dist用于存储每个点到起点的最短距离
bool st[N];  // st用于标记每个点是否已经被访问过

void add(int a, int b, ll c){
    e[idx] = b;  // 边的终点
    ne[idx] = h[a];  // 下一条相同起点的边
    w[idx] = c;  // 边的权值
    h[a] = idx++;  // 更新起点对应的第一条边
}

void dijkstra(){
    memset(dist, INF, sizeof dist);  // 初始化所有点到起点的距离为无穷大
    dist[1] = 0;  // 起点到自己的距离为0
    
    priority_queue<pii, vector<pii>, greater<pii>> heap;  // 定义一个小顶堆,用于存储待处理的点
    heap.push({0, 1});  // 将起点加入堆中
    
    while(heap.size()){
        auto t = heap.top();  // 取出堆顶元素
        heap.pop();
        
        int ver = t.second, dis = t.first;  // ver是点的编号,dis是起点到该点的距离
        if(st[ver]) continue;  // 如果该点已经被访问过,就跳过
        
        st[ver] = true;  // 标记该点已经被访问过
        
        for(int i = h[ver]; i != -1; i = ne[i]){  // 遍历所有从该点出发的边
            int j = e[i];  // j是边的终点
            
            // 如果通过这条边可以使得起点到终点的距离更短,就更新距离
            if(dist[j] > (dist[ver] ^ w[i])){
                dist[j] = dist[ver] ^ w[i];
                heap.push({dist[j], j});  // 将终点加入堆中
            }
        }
    }
}

int main()
{
    cin >> n >> m >> q;  // 输入点的数量,边的数量,询问的数量
    memset(h, -1, sizeof h);  // 初始化h数组
    
    while(m--){
        int x, y;
        ll v;
        
        scanf("%d%d%lld", &x, &y, &v);  // 输入一条边的信息
        
        add(x, y, v), add(y, x, v);  // 将这条边加入图中
    }
    
    dijkstra();  // 执行dijkstra算法,求出起点到所有点的最短距离
    
    while(q--){
        int x, y;
        scanf("%d%d", &x, &y);  // 输入一次询问
        
        // 输出起点到询问的点的最短距离
        cout << (dist[y] ^ dist[x]) << endl;
    }
}
  • 18
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值