天天看點

BZOJ 2115: [Wc2011] Xor

參考部落格:​​線性基學習筆記​​ orz

下面的題解來着該部落格

給定一個 n(n≤50000) 個點 m(m≤100000) 條邊的無向圖,每條邊上有一個權值。請你求一條從 1 到 n 的路徑,使得路徑上的邊權異或和最大。

分析

任意一條 1 到 n 的路徑的異或和,都可以由任意一條 1 到 n 路徑的異或和與圖中的一些環的異或和來組合得到。

為什麼?假如我們已經有一條 1 到 n 的路徑,考慮在出發之前,先走到圖中任意一個環上面,走一遍這個環,然後原路傳回,這樣我們既得到了這個環的異或值(走到環的路徑被走過了 2 次,抵消了),也傳回了點 1。我們可以對任意的環這樣做,進而獲得這個環的異或值。有了這個性質,不難驗證上述結論是正确的。

現在的解題思路就非常明确了,首先找出所有的環(利用 DFS 樹中的返祖邊來找環),然後找一條任意的 1 到 n 的路徑,其異或值為 s。則我們就需要選擇若幹個環,使得這些環上的異或值與 s 異或起來最大。這就轉化為線性基的問題了。

求出所有環的異或值的線性基,由于線性基的良好性質,隻需要從大到小考慮選擇每個線性基向量能否使得異或值更大即可,容易用貪心證明正确性。

證明:從高到低考慮每個二進制位,設目前的答案為 s,考慮到第 k 位,線性基向量中代表二進制位 k 的向量為 v。那麼對于第 k 位,一共有三種情況,我們分别考慮我們的選擇原則是不是正确的。

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;

typedef long long ll;
const int maxn=50000+100;

struct Edge{
  
  int to;
  ll len;
  Edge(int to_=0,ll len_=0):to(to_),len(len_){}
};
vector<Edge>G[maxn];
ll dis[maxn];
bool vis[maxn];
ll cir[maxn*5],tot;
int n,m;
int ind;

inline void AddEdge(int u,int v,ll len){
  
  G[u].push_back(Edge(v,len));
  G[v].push_back(Edge(u,len));
} 

void gauss(){
  
  for(int i=62,j;i>=0;i--){
    
    for(j=ind;j<=tot;j++) if(cir[j]&(1ll<<i)) break;
    if(j<=tot){
      
      swap(cir[j],cir[ind]);
      for(j=1;j<=tot;j++) if(j!=ind && cir[j]&(1ll<<i)) cir[j]^=cir[ind];
      ind++;
    }
  }
}

void dfs(int u){
  
  int len=G[u].size();
  vis[u]=true;
  for(int i=0;i<len;i++){
    
    Edge e=G[u][i];
    int v=e.to;
    if(!vis[v]){
      
      dis[v]=dis[u]^e.len;
      dfs(v);
    }
    else cir[++tot]=dis[u]^dis[v]^e.len;
  }
}

int main(){
  
  scanf("%d%d",&n,&m);
  for(int i=0;i<m;i++){
    
    int u,v;
    ll len;
    scanf("%d%d%lld",&u,&v,&len);
    AddEdge(u,v,len);
  }
  tot=0;
  ind=1;
  dfs(1);
  gauss();
  ll tmp=dis[n];
  for(int i=1;i<ind;i++) if((tmp^cir[i])>tmp) tmp^=cir[i];
  printf("%lld\n",tmp);
}