P4366 [Code+#4] 最短路
P4366 [Code+#4] 最短路 题解
题意简述:给一张图,边有边权,可以沿原图中的边走,花费为边权,也可以在任意两点\(x,y\)中穿梭,花费为\((x \oplus y)\times C\),\(C\)为定值,求\(s\)到\(t\)的最短路。
数据范围:\(n\le 10^5,m\le 5\times 10^5\)。
Solution
由于可以进行无数次穿梭,所以无法使用分层图最短路。
考虑第二种边有什么特殊性质。
异或$\oplus $,也被叫做二进制下的不进位加法,和加法一样满足分配律。
所以对于穿梭两次的情况,假设第一次从\(x\)到\(y\),第二次从\(y\)到\(z\),花费为\((x\oplus y)\times C+(y\oplus z)\times C\)。
如果\(x\oplus y+y\oplus z\)在二进制下不进位,那么两条边就可以合并,花费\((x\oplus y\oplus y\oplus z)\times C=(x\oplus z)*C\)。
显然,我们可以设计一个“中转站”\(y\),让\(x\)可以通过\(y\)到达任意一个点。
我们发现,如果我们要从\(x\)到\(z\),中途经过若干个中转的\(y_i\),设\(S=x\oplus y_1\oplus y_2\oplus \cdots\oplus y_{k-1}\oplus y_k\oplus z\)。
我们可以将\(S\)二进制拆位,显然对于不相同的二的次幂的加法一定不进位。(手玩一下)
于是我们让只保留\(x\)到\(x\oplus 2^k\)的边,\(x\oplus x\oplus 2^k=2^k\),所以一定会找到一条从\(x\)到\(z\)的路径。
构造完成,边数\(O(m+nlogn)\)。
如果你直接打堆优化的dijkstra,你就会获得非常多的TLE。
介绍一种更优的做法。
线段树优化 dijkstra
考虑堆优化dijkstra每次在干什么。
-
用堆找到当前未被访问过的距离最小的点。(贪心)
-
将与该点相连的边松弛,由贪心可知,这样每条边只会被松弛一次。
-
将松弛过的点加入堆。
由于每次松弛都会让一个点加入堆,而每条边只会被松弛一次,所以时间复杂度为\(O(mlogm)\)。
在这道题中,时间复杂度为大大退化成两个\(log\),无法通过,考虑优化。
由于堆保存的规模是和\(m\)有关的,考虑使用线段树来保存,让数据范围变得和\(n\)有关。
-
用线段树找到未被删除的点中最小的点。
-
由于每个点只能被更新一次,所以在线段树上将该点删除。
-
松弛,将点在线段树上更新。
这样,线段树的规模就只和\(n\)有关,和堆优化dijkstra一样,每条边只会被松弛一次,所以时间复杂度为\(O(nlogm)\)。
P.S.线段树常数过大,有时候甚至会反向优化,慎用。而在这题中边数带\(log\),所以优化效果明显一点。
tips:0 号点会在异或会用上,所以线段树的范围为\([0,n]\)。
代码如下:
#include<bits/stdc++.h>
#define lc (p<<1)
#define rc (p<<1|1)
#define mid ((L+R)>>1)
using namespace std;
const int N=2e5+10,M=1e7+10;
int head[N],ver[M],edge[M],nxt[M],tot=1;
void add(int x,int y,int z){
ver[++tot]=y,edge[tot]=z,nxt[tot]=head[x],head[x]=tot;
}
int d[N],n,m,dat[N<<2],C,s,t,ans;
bool v[N];
void upd(int p){
dat[p]=0x3f3f3f3f;
if(dat[lc]!=-1)dat[p]=min(dat[p],dat[lc]);
if(dat[rc]!=-1)dat[p]=min(dat[p],dat[rc]);
}
void build(int p,int L,int R){
dat[p]=0x3f3f3f3f;
if(L==R)return;
else build(lc,L,mid),build(rc,mid+1,R),upd(p);
}
void change(int p,int L,int R,int x,int k){
if(L==R){dat[p]=min(dat[p],k);return;}
if(x<=mid)change(lc,L,mid,x,k);
else change(rc,mid+1,R,x,k);
upd(p);
}
int ask(int p,int L,int R){
if(L==R){ans=dat[p];return L;}
if(dat[p]==dat[lc])return ask(lc,L,mid);
else if(dat[p]==dat[rc])return ask(rc,mid+1,R);
else return -1;
}
int main(){
scanf("%d%d%d",&n,&m,&C);
for(int i=1,x,y,z;i<=m;i++)scanf("%d%d%d",&x,&y,&z),add(x,y,z);
scanf("%d%d",&s,&t);
build(1,0,n),change(1,0,n,s,0);
while(!v[t]){
int x=ask(1,0,n);
v[x]=1;d[x]=ans;
change(1,0,n,x,-1);
for(int i=head[x];i;i=nxt[i]){
int y=ver[i];
if(!v[y])change(1,0,n,y,d[x]+edge[i]);
}
for(int k=1;k<=n;k<<=1)
if(!v[x^k]&&(x^k)<=n)change(1,0,n,x^k,d[x]+C*k);
}
printf("%d\n",d[t]);
return 0;
}

浙公网安备 33010602011771号