异色最短路
题目描述
一个无向图中有 N 个点 M 条边。其中第 i 个点的颜色是 A[i](1 \(\leq\) A[i] \(\leq\) K);第 j 条边的边权是 C[j],端点是 U[j] 和 V[j]。
N 个点中有 L 个点是特殊点(B[1] ~ B[L])。求图中每个点到任意异色特殊点的最短路。
输入格式
N M K L
A[1] ... A[N]
B[1] ... B[L]
U[1] V[1] C[1]
U[2] V[2] C[2]
.
.
U[M] V[M] C[M]
输出格式
N 个空格隔开的数。第 i 个数表示从点 i 出发到任意异色特殊点的最短路。若无解输出-1。
样例输入
4 4 2 2 1 1 2 2 2 3 1 2 15 2 3 30 3 4 40 1 4 10
样例输出
45 30 30 25
数据范围
\(2 \leq N \leq 10^5\)
\(1 \leq M \leq 10^5\)
\(1 \leq K \leq 10^5\)
\(1 \leq L \leq N\)
\(1 \leq C[i] \leq 10^9\)
\(1 \leq B[1] < B[2] < ... < B[L] \leq N\)
对于 \(10\%\) 的数据,有 \(L = 1\)
对于另外\(20\%\) 的数据,有 \(K = 2\)
题解:

#include <bits/stdc++.h>
using namespace std;
#define N 200000
#define INF (ll)1e+18
#define rep(i, n) for(int i = 0; i < n; ++i)
#define pb push_back
#define ll long long
#define pll pair<long long ,long long>
int main() {
int n, m, k, l;
int x, y, sz;
ll z;
cin >> n >> m >> k >> l;
vector<int>a(n);
vector<vector<pair<int, ll> > >e(n);
vector<int>used(n, 0);
vector<ll>d(n, INF);
vector<int>cit(n);
vector<ll>dd(n, INF);
priority_queue<tuple<ll, int, int> >pq;
tuple<ll, int, int>t;
rep(i, n)cin >> a[i];
rep(i, l) {
cin >> x;
pq.push({ 0LL,x - 1,a[x - 1] });
}
rep(i, m) {
cin >> x >> y >> z;
e[x - 1].pb({ y - 1,z });
e[y - 1].pb({ x - 1,z });
}
while (!pq.empty()) {
t = pq.top();
pq.pop();
z = get<0>(t);
x = get<1>(t);
y = get<2>(t);
// cout<<x<<" "<<y<<" "<<z<<endl;
if ((used[x] >= 0) && (used[x] != y)) {
if (used[x] == 0) {
d[x] = -z;
cit[x] = y;
used[x] = y;
}
else {
dd[x] = -z;
used[x] = -1;
}
sz = e[x].size();
rep(i, sz)pq.push({ z - e[x][i].second,e[x][i].first,y });
}
}
rep(i, n) {
if (d[i] >= INF)d[i] = -1;
if (dd[i] >= INF)dd[i] = -1;
}
rep(i, n) {
if (a[i] != cit[i])cout << d[i];
else cout << dd[i];
if (i < (n - 1))cout << " ";
else cout << endl;
}
return 0;
}
注释后的代码:
#include <bits/stdc++.h>
using namespace std;
// 宏定义部分
#define N 200000 // 最大节点数(未在代码中直接使用,仅作示例宏)
#define INF (ll)1e+18 // 设定一个足够大的“无穷大”值,用于初始化距离数组
#define rep(i, n) for(int i = 0; i < n; ++i) // 简写 for 循环:i 从 0 迭代到 n-1
#define pb push_back // 简写 push_back
#define ll long long // 简写 long long 为 ll
#define pll pair<long long, long long> // 简写 pair<ll, ll> 为 pll
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
// 1. 读入基本参数
int n, m, k, l;
// n: 节点数,m: 边数,k: 颜色数(K),l: 特殊点数(L)
cin >> n >> m >> k >> l;
int x, y, sz;
ll z;
// 2. 读入每个节点的颜色 a[i]
// a[i] 的取值范围是 [1..k]
vector<int> a(n);
rep(i, n) cin >> a[i];
// 3. 建立邻接表存储图的边
// e[u] 中存放 (v, w),表示 u->v 边,权重为 w
vector<vector<pair<int, ll>>> e(n);
rep(i, m) {
cin >> x >> y >> z;
// 输入的节点编号是 1..n,转为 0..n-1
x--;
y--;
e[x].pb({y, z});
e[y].pb({x, z});
}
// 4. 多源 Dijkstra 需要用到的数组/容器
// used[i] 记录节点 i 被哪些颜色的特殊点访问过:
// used[i] == 0: 表示尚未被任何特殊点访问
// used[i] > 0: 表示曾经被颜色 used[i] 的特殊点访问过且已经记录到 d[i]
// used[i] == -1: 表示已经被至少两种不同颜色访问过(并且 d[i] 和 dd[i] 都已记录)
vector<int> used(n, 0);
// d[i] 用来记录“节点 i 第一次被访问的最小距离”,对应的颜色存到 cit[i]
// dd[i] 用来记录“节点 i 第二次被访问且颜色不同的距离”(即第二小距离)
vector<ll> d(n, INF), dd(n, INF);
vector<int> cit(n, -1); // cit[i] 记录第一个访问 i 的特殊点的颜色
// 优先队列 pq 中存放 (距离 z, 节点 x, 来源颜色 y)
// 注意:这里没有自定义比较函数,std::tuple 默认按第一个元素降序排列(最大堆)
// 为让最小距离优先弹出,代码里把“距离”存成负值 —— z 越小(更负)实际上表示距离越大
// 但后面会看到推入时做了 -weight 操作,使得向 pq 推入的是负的累计距离,从而实现“最小距离先出”的效果
priority_queue<tuple<ll, int, int>> pq;
tuple<ll, int, int> t;
// 5. 将所有 L 个特殊点当作多源 Dijkstra 的起点,距离初始化为 0
rep(i, l) {
cin >> x; // 读入特殊点编号(1..n)
x--; // 转为 0..n-1
int color = a[x]; // 该特殊点的颜色
// 距离初始化为 0,且来源颜色为 color
// 因为优先队列是最大堆,所以我们存入 (0LL, x, color),真实意义是“距离 = 0”
pq.push({ 0LL, x, color });
}
// 6. 多源 Dijkstra 主循环
while (!pq.empty()) {
t = pq.top();
pq.pop();
// 解包:z 存的是“负的累计距离”,x 是当前节点,y 是来源颜色
z = get<0>(t);
x = get<1>(t);
y = get<2>(t);
// 6.1 判断是否为“过时状态”
// 如果 used[x] >= 0 且 used[x] != y,说明:
// - used[x] == 0: 节点 x 之前没有被访问过,这时视为“有效”
// - used[x] == 某个颜色 c 且 c != y: 表示 x 之前被颜色 c 访问过,这次是颜色 y 再次访问
// - used[x] == -1: 表示已有两种颜色记录过 x,任意新的访问都可看作“多余”?
// 实际判断在下面 if 语句里:
if ((used[x] >= 0) && (used[x] != y)) {
// 说明当前状态 (x, z, y) 所代表的“来自颜色 y 的距离”对 x 是有用的
if (used[x] == 0) {
// 6.2 第一次遇到节点 x
// 把 d[x] 记录为 -z(z 存的是负距离,故 -z 恰为正距离)
d[x] = -z;
cit[x] = y; // 记录第一个访问 x 的颜色
used[x] = y; // 标记 x 已被颜色 y 访问过
}
else {
// 6.3 第二次遇到节点 x,且来源颜色 y != used[x]
// 此时把 dd[x] 记录为 -z,表示第二小距离;并把 used[x] 置 -1(表示已被两种不同颜色访问)
dd[x] = -z;
used[x] = -1;
}
// 6.4 继续松弛 x 的所有邻边,把“新的负距离”推入 pq
sz = e[x].size();
rep(i, sz) {
int v = e[x][i].first;
ll w = e[x][i].second;
// 新的累计距离要加上这条边的权重 w
// 因为 z 已经是“负距离”,所以加边权后仍要保持“负数”表示法:z - w
pq.push({ z - w, v, y });
}
}
// 否则,如果 used[x] < 0(即 -1,表示已被两种颜色访问过),或者 used[x] == y(同一种颜色重复访问),
// 则认为当前 (x,z,y) 是“过时”或“无效”,不做任何操作。
}
// 7. 将未访问到的节点标记为 -1
rep(i, n) {
if (d[i] >= INF) d[i] = -1; // 如果 d[i] 仍是 INF,说明连同色特殊点也没到达过
if (dd[i] >= INF) dd[i] = -1; // 如果 dd[i] 仍是 INF,说明只被一种颜色到达过或根本未到达过
}
// 8. 根据题意输出每个节点到“异色特殊点”的最短距离
// 若节点 i 的颜色 a[i] 与第一个访问它的颜色 cit[i] 不同,
// 则说明 d[i] 就是第一条“异色特殊点”路径长度
// 否则(说明第一条是同色),就输出第二条 dd[i]
rep(i, n) {
if (a[i] != cit[i])
cout << d[i];
else
cout << dd[i];
if (i < n - 1) cout << " ";
else cout << "\n";
}
return 0;
}

浙公网安备 33010602011771号