最短路
题意:
-
给定一个完全图,图中任意点之间都有一条边,边权为点编号异或的数值 * C
-
然后求某点到某点的最短路。
思路:
- 观察数据范围,可以看到根本存不下图中所有的边。
- 这就启发我们思考,是不是有的边重复,能不能舍去一些无用的边,是否能从数学角度去累计。
- 答案是可以的,我们知道一串连续的二进制数之间相互组合,可以凑出某个上限内所有的数字。
- 所以此题就对边权二进制分类。我们取出图中所有权值为 2 的 次 方 2的次方 2的次方 的边,根据上面的理论,用这些边就可以走出所有最短路。
C o d e : Code: Code:
#include<bits/stdc++.h>
#include<unordered_set>
#include<unordered_map>
#define mem(a,b) memset(a,b,sizeof a)
#define cinios (ios::sync_with_stdio(false),cin.tie(0),cout.tie(0))
#define sca scanf
#define pri printf
#define ul (u << 1)
#define ur (u << 1 | 1)
//#define x first
//#define y second
//#pragma GCC optimize(2)
//[博客地址](https://blog.csdn.net/weixin_51797626?t=1)
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
typedef pair <ll, PII> PI;
const int N = 300010, M = 6000010, MM = N;
int INF = 0x3f3f3f3f, mod = 100003;
ll LNF = 0x3f3f3f3f3f3f3f3f;
int n, m, k, T, S, D;
int h[N], e[M], ne[M], w[M], idx;
int dist[N];
bool st[N];//点数边数记得开多点,可能会用到编号比 n 大的点转移
void add(int a, int b, int x) {
e[idx] = b, ne[idx] = h[a], w[idx] = x, h[a] = idx++;
}
int dj() { //图建好,最短路正常走就行
priority_queue<PII, vector<PII>, greater<PII>> q;
mem(dist, 0x3f);
dist[S] = 0;
q.push({ 0,S });
while (q.size())
{
PII t = q.top();
q.pop();
int ver = t.second;
if (st[ver])continue;
st[ver] = true;
for (int i = h[ver]; ~i; i = ne[i]) {
int j = e[i];
if (dist[j] > dist[ver] + w[i]) {
dist[j] = dist[ver] + w[i];
q.push({ dist[j],j });
}
}
}
return dist[D];
}
int main() {
cinios;
cin >> n >> m >> k;
mem(h, -1);
for (int i = 0; i < m; i++) {
int a, b, x;
cin >> a >> b >> x;
add(a, b, x);
}
//任意两个 n 以内的数异或起来,最大的二进制位第一个 1 的位置不变
for (int i = 1; i <= n; i++)
for (int j = 0; (1 << j) <= n; j++) {
//枚举 2 的次方
int g = 1 << j;
//根据异或性质我们就可以得到 i 对应的 j == i ^ g
add(i, i ^ g, g * k);
add(i ^ g, i, g * k);
}
cin >> S >> D;
cout << dj();
return 0;
}
/*
*/