題解 [WC2011] 最大XOR和路徑
題目連結
題目描述
給定 \(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\)
那如果我們選的簡單路徑是 \(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;
}