异色最短路

题目描述

一个无向图中有 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\)

题解:
image

#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;
}

posted @ 2025-06-03 13:00  katago  阅读(20)  评论(0)    收藏  举报