#P3757. 第3题-米小游的异或最短路
          
                        
                                    
                      
        
              - 
          
          
                      1000ms
            
          
                      Tried: 5
            Accepted: 2
            Difficulty: 7
            
          
          
          
                       所属公司 : 
                              米哈游
                                
            
                        
              时间 :2025年9月21日
                              
                      
          
 
- 
                        算法标签>最短路算法          
 
第3题-米小游的异或最短路
解题思路
给定一条从 1 到 u 的简单路径,允许至多一次选择一个连续边段 [l,r] 把该段内每条边权 w 改为 w⊕x 后再求路径边权和的最小值。等价地看:沿着路径行进时,我们会有三种“状态”:
- 状态 0:尚未开始异或段(之后的某一段可开始,也可以永不开始);
 - 状态 1:正处于异或段中(本段内边权以 w⊕x 计);
 - 状态 2:已结束异或段(之后再以原权 w 行走,不可再开启)。
 
把“路径上一段用 w⊕x”的限制,转化为分层图最短路(Dijkstra):
- 
把原图每个点 v 复制三层 (v,0),(v,1),(v,2) 分别代表上述三状态;
 - 
对每条原边 (u→v,w) 添加如下 6 条转移(均为非负权,适用 Dijkstra):
- (u,0)→(v,0):成本 w(仍未开始)
 - (u,0)→(v,1):成本 w⊕x(在此边“开启”异或段)
 - (u,0)→(v,2):成本 w⊕x(异或段长度为 1,开启并在此边“结束”)
 - (u,1)→(v,1):成本 w⊕x(继续处于段内)
 - (u,1)→(v,2):成本 w⊕x(以此边作为段内最后一条边,随后进入已结束)
 - (u,2)→(v,2):成本 w(已经结束,仅按原权前进)
 
 
为何需要 (u,0)→(v,2)?为了允许“中间某一条边单独作为异或段”(长度为 1 且不在末尾)。若没有这条边,只能在下一条边结束,导致段长 ≥2。
起点为 (1,0)(尚未开始,代价 0)。对每个结点 i,允许在任意状态结束(段可以没开、在最后一条边结束、或已经结束),因此答案为
$$\min\{\operatorname{dist}(i,0),\operatorname{dist}(i,1),\operatorname{dist}(i,2)\}, $$若均不可达则输出 −1。
算法要点:分层建图(3 倍结点、约 6 倍边),全程边权非负,直接 Dijkstra。
复杂度分析
- 结点数:3n;边数:约 6m。
 - Dijkstra 时间复杂度:O((n+m)logn)(精确为 O((3n+6m)log(3n)))。
 - 空间复杂度:O(n+m)(存 3 层距离与 6 倍边)。
 
代码实现
Python
# 分层图 + Dijkstra;允许在任意状态结束
import sys
import heapq
INF = 10**30
def dijkstra(n, edges, x):
    # 构建 3 层图:节点编号为 0..3*n-1,层偏移为 off=layer*n
    N = n * 3
    g = [[] for _ in range(N)]
    for u, v, w in edges:
        u -= 1; v -= 1
        wx = w ^ x
        # (u,0)->(v,0/1/2)
        g[u + 0*n].append((v + 0*n, w))
        g[u + 0*n].append((v + 1*n, wx))
        g[u + 0*n].append((v + 2*n, wx))  # 段长=1
        # (u,1)->(v,1/2)
        g[u + 1*n].append((v + 1*n, wx))
        g[u + 1*n].append((v + 2*n, wx))  # 在此边结束
        # (u,2)->(v,2)
        g[u + 2*n].append((v + 2*n, w))
    dist = [INF] * N
    start = 0  # (1,0)
    dist[start] = 0
    pq = [(0, start)]
    while pq:
        d, u = heapq.heappop(pq)
        if d != dist[u]:
            continue
        for v, w in g[u]:
            nd = d + w
            if nd < dist[v]:
                dist[v] = nd
                heapq.heappush(pq, (nd, v))
    # 取每个原节点三层的最小值
    ans = []
    for i in range(n):
        best = min(dist[i + 0*n], dist[i + 1*n], dist[i + 2*n])
        ans.append(-1 if best >= INF//2 else best)
    return ans
def main():
    data = list(map(int, sys.stdin.buffer.read().split()))
    it = iter(data)
    n = next(it); m = next(it); x = next(it)
    edges = []
    for _ in range(m):
        u = next(it); v = next(it); w = next(it)
        edges.append((u, v, w))
    res = dijkstra(n, edges, x)
    print(" ".join(map(str, res)))
if __name__ == "__main__":
    main()
Java
// 分层图 + Dijkstra;不使用快读,采用 Scanner 读入
import java.util.*;
public class Main {
    static final long INF = (long)4e18;
    // 加边(数组邻接表)
    static void addEdge(int[] head, int[] to, long[] w, int[] next, int[] idxRef, int u, int v, long ww) {
        int idx = idxRef[0];
        to[idx] = v;
        w[idx] = ww;
        next[idx] = head[u];
        head[u] = idx;
        idxRef[0] = idx + 1;
    }
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();
        long x = sc.nextLong();
        int N = n * 3;        // 三层节点
        int M = m * 6;        // 每条原边扩展 6 条
        int[] head = new int[N];
        Arrays.fill(head, -1);
        int[] to = new int[M];
        long[] w = new long[M];
        int[] next = new int[M];
        int[] idxRef = new int[]{0}; // 辅助记录当前边下标
        for (int i = 0; i < m; i++) {
            int u = sc.nextInt();
            int v = sc.nextInt();
            long ww = sc.nextLong();
            u--; v--;
            long wx = ww ^ x;
            // (u,0)->(v,0/1/2)
            addEdge(head, to, w, next, idxRef, u + 0*n, v + 0*n, ww);
            addEdge(head, to, w, next, idxRef, u + 0*n, v + 1*n, wx);
            addEdge(head, to, w, next, idxRef, u + 0*n, v + 2*n, wx);  // 段长=1
            // (u,1)->(v,1/2)
            addEdge(head, to, w, next, idxRef, u + 1*n, v + 1*n, wx);
            addEdge(head, to, w, next, idxRef, u + 1*n, v + 2*n, wx);  // 在此边结束
            // (u,2)->(v,2)
            addEdge(head, to, w, next, idxRef, u + 2*n, v + 2*n, ww);
        }
        // Dijkstra
        long[] dist = new long[N];
        Arrays.fill(dist, INF);
        boolean[] vis = new boolean[N];
        PriorityQueue<long[]> pq = new PriorityQueue<>(Comparator.comparingLong(a -> a[0]));
        int start = 0; // (1,0)
        dist[start] = 0;
        pq.add(new long[]{0L, start});
        while (!pq.isEmpty()) {
            long[] cur = pq.poll();
            long d = cur[0];
            int u = (int)cur[1];
            if (vis[u]) continue;
            vis[u] = true;
            for (int e = head[u]; e != -1; e = next[e]) {
                int v = to[e];
                long nd = d + w[e];
                if (nd < dist[v]) {
                    dist[v] = nd;
                    pq.add(new long[]{nd, v});
                }
            }
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < n; i++) {
            long best = Math.min(dist[i + 0*n], Math.min(dist[i + 1*n], dist[i + 2*n]));
            if (best >= INF / 2) sb.append(-1);
            else sb.append(best);
            if (i + 1 < n) sb.append(' ');
        }
        System.out.println(sb.toString());
        sc.close();
    }
}
C++
// 分层图 + Dijkstra(数组链式前向星)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll INF = (ll)4e18;
struct Edge {
    int to, next;
    ll w;
};
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, m;
    long long x;
    if (!(cin >> n >> m >> x)) return 0;
    int N = n * 3;
    int M = m * 6;                 // 每条原边扩展 6 条
    vector<int> head(N, -1);
    vector<Edge> es; es.reserve(M);
    auto add = [&](int u, int v, ll w) {
        es.push_back({v, head[u], w});
        head[u] = (int)es.size() - 1;
    };
    for (int i = 0; i < m; ++i) {
        int u, v; ll w;
        cin >> u >> v >> w;
        --u; --v;
        ll wx = w ^ x;
        // (u,0)->(v,0/1/2)
        add(u + 0*n, v + 0*n, w);
        add(u + 0*n, v + 1*n, wx);
        add(u + 0*n, v + 2*n, wx);       // 段长=1
        // (u,1)->(v,1/2)
        add(u + 1*n, v + 1*n, wx);
        add(u + 1*n, v + 2*n, wx);       // 在此边结束
        // (u,2)->(v,2)
        add(u + 2*n, v + 2*n, w);
    }
    // Dijkstra
    vector<ll> dist(N, INF);
    vector<char> vis(N, 0);
    priority_queue<pair<ll,int>, vector<pair<ll,int>>, greater<pair<ll,int>>> pq;
    int s = 0; // (1,0)
    dist[s] = 0;
    pq.push({0, s});
    while (!pq.empty()) {
        auto [d, u] = pq.top(); pq.pop();
        if (vis[u]) continue;
        vis[u] = 1;
        for (int i = head[u]; i != -1; i = es[i].next) {
            int v = es[i].to;
            ll nd = d + es[i].w;
            if (nd < dist[v]) {
                dist[v] = nd;
                pq.push({nd, v});
            }
        }
    }
    for (int i = 0; i < n; ++i) {
        ll best = min(dist[i + 0*n], min(dist[i + 1*n], dist[i + 2*n]));
        if (best >= INF / 2) cout << -1;
        else cout << best;
        if (i + 1 < n) cout << ' ';
    }
    cout << '\n';
    return 0;
}
        题目内容
给定一个有 n 个结点以及 m 条的带权有向图 G ,以及一个整数 x 。G 中保证没有自环。
米小游初始在结点 1。对于一条从 1 到 u 的长度k 为的 简单路径,设其经过的 点 为p1,p2,...,pk+1,其中 p1=1,pk+1=u ,且对于所有 1≤i≤k,G 中都存在一条 pi 从到 pi+1 的有向边。这条路径的代价计算方式如下:
- 米小游可以选择两个整数 l,r ,满足 1≤l≤r≤k 。接下来,对于所有的 l≤i≤r,米小游将到这条有向边的边权异或上 x 。也就是说,如果原本这条边的边权为 w ,米小游会将其修改为 w⊕x ,其中 ⊕ 代表按位异或。本操作最多执行一次。
 - 路径的代价即为修改之后,所有其经过的边的边权和。
 - 从结点 1 到 i 的最小代价即为所有可能的从结点 1 到 i 的路径中的最小代价。
 
米小游想知道,在这样的代价计算方式下,他到过每个结点的最小代价分别是多少。
注意:为某条路径选定并执行异或操作后,仅用于计算该路径的代价;图本身不会被真正修改,评估其他路径时可重新自由选择 l,r ,或不执行此操作。
【名词解释】
简单路径:不经过重复点和重复边的路径。
输入描述
第一行输入三个整数 n,m,x(2≤n≤105,1≤m≤2⋅105,0≤x≤109) ,分别表示 G 中的结点数量、边数,以及异或时常数。 接下来 m 行,每行输入三个整数 ui,vi,wi(1≤ui,vi≤n,0≤wi≤109) ,代表一条从结点 ui 到 vi,边权为 wi 的有向边。输入保证 G 中不存在自环。
输出描述
输出一行 n 个整数,其中第 i 个整数代表从结点 1 到 i 的路径的最小代价,无法到达则第 i 个整数为 −1 。
样例1
输入
5 5 2
1 2 2
1 5 3
5 3 4
2 4 10
1 4 9
输出
0 0 5 8 1
说明
对于结点 4 ,米小游可以选择路径 1→2→4 ,并将 1→2 和 2→4 两条有向边的边权异或上,代价为 (2⊕2)+(10⊕2)=0+8=8 。可以证明不存在代价小于 8 的路径。 对于结点 3,米小游可以选择路径 1→5→3,并将 1→5 这条有向边的边权异或上 x ,代价为 (3⊕2)+4=1+4=5。可以证明不存在代价小于 5 的路径。