1. 程式人生 > 其它 >題解 [WC2011] 最大XOR和路徑

題解 [WC2011] 最大XOR和路徑

題目連結

Luogu

題目描述

給定 \(n\) 個點 , \(m\) 條邊的帶權無向圖 , 問所有 \(1\)\(n\) 的路徑中 , 邊權異或和最大是多少

其中 \(n \le 50000, m \le 100000\) , 且可能有重邊和自環

分析

首先 , 看到最大異或和 , 我們很自然的想到用線性基去處理

但線性基對最大異或和的限制十分嚴格 , 需要數字之間能任意異或 , 而本題需要選取的邊是一條 \(1\)\(n\) 的路徑

如何把題目轉化成可以隨便異或的形式呢 ?

仔細觀察會發現 , 所有簡單路徑都可以等價表示為任意一條 \(1\)\(n\) 的簡單路徑加上一個過 \(n\) 點的環

如路徑 \(1 -> 3 -> 4\) 可以表示為 \(1 -> 2 -> 4 -> 2 -> 1 -> 3 -> 4\)

其中 \(4 -> 2 -> 1 -> 3 -> 4\) 就是一個過點 \(4\) 的環

那我們再考慮不是簡單路徑的路徑

路徑 \(1 -> 3 -> 4 -> 5 -> 6 -> 4 -> 3 -> 7\)\(3 -> 4\) 這條邊經過了兩次 , 由異或運算的性質可知這條邊不計入貢獻 , 所以這條路徑可以表示為簡單路徑 \(1 -> 3 -> 7\)

再加上 不過\(7\) 的環 \(4 -> 5 -> 6 -> 4\)

那如果我們選的簡單路徑是 \(1 -> 2 -> 7\) 那剛剛那條路徑可以表示為簡單路徑 \(1 -> 2 -> 7\) 加上環 \(7 -> 2 -> 1 -> 3 -> 7\) 再加上環 \(4 -> 5 -> 6 -> 4\)

也就是一條好長好長的路徑 \(1 -> 2 -> 7 -> 2 -> 1 -> 3 -> 7 -> 3 -> 4 -> 5 -> 6 -> 4 -> 3 -> 7\)


其中 \(1 -> 2, 2 -> 7, 3 -> 4\) 經過了兩次 , \(3 -> 7\) 被經過了三次

所以我們猜測所有 \(1\)\(n\) 的路徑都可以表示為 任意一條 \(1\)\(n\) 的簡單路徑加上任意個環

而當圖聯通的情況下 , 任意一條 \(1\)\(n\) 的簡單路徑加上任意個環也顯然可以表示一條 \(1\)\(n\) 的路徑

這樣我們終於把題目轉化成了線性基能做的形式

只要我們把所有環插入線性基內 , 再以任意一條簡單路徑為初始值跑一遍線性基 , 最大異或和就是答案啦

但是我們發現 , 當可能出現重邊時 , 圖中的環的個數是指數級別的

那如何將這些環放進線性基裡呢 ?

仔細觀察發現 , 有些環可以等價的用其他環表示

如環 \(1 -> 2 -> 4 -> 3 -> 1\) 可以表示為環 \(1 -> 2 -> 3 -> 1\) 加上環 \(3 -> 2 -> 4 -> 3\)
即路徑 \(1 -> 2 -> 3 -> 1 -> 3 -> 2 -> 4 -> 3 -> 1\) , 其中 \(2 -> 3\) 經過兩次 , \(1 -> 3\) 經過三次

也就是說 , 一個無向連通圖存在一個由若干個環構成的基 , 且這張圖中所有的環都可以由基中的環異或得到

那這個基又是什麼呢 ?

個人感覺這裡是這道題最神奇的地方 , 我實在是編不出這個解法的腦回路 , 於是只能直接講結論了

建出無向圖的 DFS 樹 , 基就是那些 由一條非樹邊和若干條樹邊構成的環的集合

顯然 , 一條非樹邊和若干條樹邊只能構成一個環
我們假設由非樹邊 \(e\) 和若干條樹邊構成的環的異或和為 \(v_e\)

首先 , 對於僅由樹邊構成的環 , 這樣的環不存在
然後 , 對於一條非樹邊和若干條樹邊構成的環 , 它就在基裡面
最後 , 對於由若干條非樹邊 \(e_1, e_2, ... e_k\) 和若干條樹邊構成的環 , 他的異或和就是 \(v_{e_1} \oplus v_{e_2} \oplus .... \oplus v_{e_k}\)

因為兩個環異或就相當於他們的邊集的並再去掉邊集的交

非樹邊共 \(m - n + 1\) 條 , 所以基中的環有 \(m - n + 1\) 個 , 將他們插進線性基中 , 複雜度為 \(O((m - n) \log V)\) 其中 \(V\) 為邊權大小

至此本題解決

程式碼

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

const int N = 50010;
const int M = 200010;

LL n, m, cnt, k, head[N], to[M], nxt[M], w[M], st[N], d[N], vis[N]; // st為線性基 , d為DFS樹上點的深度(d[n]當然就是一條簡單路徑的長度)

inline void addedge(int u, int v, LL k)
{
    to[++cnt] = v;
    nxt[cnt] = head[u], head[u] = cnt, w[cnt] = k;
}

void add(LL x)
{
    if(!x) return ;
    for(int i = 63; i >= 0; i--){
        if(!(x >> i)) continue;
        if(!st[i]){
            st[i] = x;
            break;
        }
        x ^= st[i];
    }
}

LL que(LL x)
{
    for(int i = 63; i >= 0; i--)
        if((x ^ st[i]) > x) x ^= st[i];
    return x;
}

void dfs(int x, LL dist)
{
    d[x] = dist, vis[x] = 1;
    for(int i = head[x]; i; i = nxt[i]){
        if(!vis[to[i]]) dfs(to[i], dist ^ w[i]);  // 如果to[i]未被訪問 , 說明i是樹邊
        else add(w[i] ^ dist ^ d[to[i]]); // i是非樹邊 , to[i] 和 x 的 lca 以上的邊異或了兩次 , 留下的就只有一個環
    }
}

int main()
{
    ios :: sync_with_stdio(false);
    cin >> n >> m;
    for(int i = 1, u, v; i <= m; i++){
        cin >> u >> v >> k;
        addedge(u, v, k);
        addedge(v, u, k);
    }
    dfs(1, 0);
    printf("%lld\n", que(d[n]));
    return 0;
}