基础最短路练习题
题目链接
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 1≤x,y≤n),表示一条连接 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 1≤x,y≤n),表示一次询问。
输出格式
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=n−1 |
7 , 8 7,8 7,8 | 10 5 {10}^5 105 | v ≤ 1 v \le 1 v≤1 |
9 , 10 9,10 9,10 | 10 5 {10}^5 105 | 无 |
对于 100 % 100\% 100% 的数据,满足 1 ≤ n ≤ 10 5 1 \le n \le {10}^5 1≤n≤105, 1 ≤ m ≤ 2 n 1 \le m \le 2n 1≤m≤2n, 0 ≤ v < 2 30 0 \le v < 2^{30} 0≤v<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 a⊕a=0
- 对于任何数 a a a,都有 a ⊕ 0 = a a ⊕ 0 = a a⊕0=a
- 这意味着,一个数与自己进行异或运算会得到0,一个数与0进行异或运算还是它自己。
- 由上述性质可得:
- 异或运算的逆运算其实就是异或运算本身。
- 如果你有一个数 a a a,并且你知道 a a a 与另一个数 b b b 的异或结果 c c c(即 c = a ⊕ b c = a ⊕ b c=a⊕b),那么你可以通过再次进行异或运算来找出 a a a(即 a = c ⊕ b a = c ⊕ b a=c⊕b)。
- 所以我们由异或性质可以得到本题的结局方案:
- 以
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 的异或值。
- 以
1
1
1 为源点找所有点的单源最短路,询问为
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;
}
}