题意:求带边权无向图上 \(1\) 到 \(n\) 的异或最短路,可以重复经过某条边。
首先,我们考虑从 \(x\) 到 \(y\) 的路径 \(A\),它的权值是 \(a\)。我们从路径中途的某个地方离开路径,来到一个地方,然后回到这个路径(因为路径的结尾是 \(y\),到 \(y\) 的路径一定会回到这个 \(A\))。
-
如果离开和回到原先路径是同一个地方,那么我们考虑其行进的轨迹,一定是出发,到达一个环,走一遍环,回来。而因为到达环和从环回来的路径权值都被计算了两次,等于没有贡献。所有的贡献就是环上的异或和。
-
如果回到路径在离开路径节点 \(u\) 之后的节点 \(v\),等于 \(u\) 到 \(v\) 的路段不贡献, \(u\) 到 \(v\) 的另一条路径贡献。但是这等价于把 \(a\) 异或上 \(u\rightarrow v\rightarrow u\) 的环的异或和。
-
如果回到路径 \(v\) 在离开路径 \(u\) 之前,则 \(v\) 到 \(u\) 的路径贡献两次等于不贡献,和前面是同一种情况。
我们发现,所有的贡献都是把一个环的异或和异或进总的贡献值。
那是不是所有的环都可以呢?很明显可以。我们先从 \(a\) 到 \(b\),走一个环到 \(b\) 回到 \(a\),就是把这个环贡献上了,因为任意的环都是可到达的,所以任意的环都是可贡献的。
然后我们考虑如何计算所有的环。我们考虑 dfs,因为无向图的 dfs 生成树只有树边和返祖边,我们就可以利用这一点。然后如果是由两个小环拼成的大环,则可以直接等价于两个小环的异或和。那么我们就只需要考虑最基础的环——一条返祖边和它两端点之间的树边生成的环。
这样的环可以直接 dfs 求出,我们在 dfs 的同时记录当前节点到 \(1\) 的异或和,因为异或的性质,我们一旦发现 \(in queue\) 的点就直接把当前异或和和原节点异或和异或起来就是环上的结果。(因为从 \(1\) 到环的结果被异或了两次)然后我们把这个结果丢进线性基。
最后,找到一条 \(1\) 到 \(n\) 的路径(就是 \(n\) 到 \(1\) 的异或和),在线性基里查一下最大值。
如果最优结果不是这条路径怎么办呢?其实,假设两条路径 \(1\rightarrow a\rightarrow n\) 和 \(1\rightarrow b\rightarrow n\),那么它们组合成一个环 \(1\rightarrow b\rightarrow n\rightarrow a\rightarrow 1\),这样的情况也被我们考虑进去了,不会遗漏,所以一开始随便选路径即可。
int n,m,a,b,c,vtt[100005],vis[100005],inq[100005],p[35];
vt<pii>vv[100005];
inline void ins(int x){
per(j,0,30)if(x>>j&1){
if(!p[j]){
p[j]=x;
return;
}else x^=p[j];
}
}
inline int mx(int x){
int res=x;
per(j,0,30)if((res^p[j])<res)res^=p[j];
return res;
}
inline void dfs(int x,int p){
inq[x]=1;vtt[x]=1;
for(auto j:vv[x])if(j.first!=p){
if(inq[j.first]){
ins(vis[x]^vis[j.first]^j.second);
}else if(!vtt[j.first]){
vis[j.first]=vis[x]^j.second;
dfs(j.first,x);
}
}inq[x]=0;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n>>m;
rp(i,m){
cin>>a>>b>>c;
vv[a].pb({b,c});
vv[b].pb({a,c});
}vis[1]=0;
dfs(1,0);
cout<<mx(vis[n])<<endl;
return 0;
}
//Crayan_r
标签:int,路径,dfs,贡献,异或,Path,Problem,CF845G,rightarrow
From: https://www.cnblogs.com/jucason-xu/p/17149056.html