P4366 [Code+#4] 最短路

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每次在干什么。

  1. 用堆找到当前未被访问过的距离最小的点。(贪心)

  2. 将与该点相连的边松弛,由贪心可知,这样每条边只会被松弛一次。

  3. 将松弛过的点加入堆。

由于每次松弛都会让一个点加入堆,而每条边只会被松弛一次,所以时间复杂度为\(O(mlogm)\)

在这道题中,时间复杂度为大大退化成两个\(log\),无法通过,考虑优化。

由于堆保存的规模是和\(m\)有关的,考虑使用线段树来保存,让数据范围变得和\(n\)有关。

  1. 用线段树找到未被删除的点中最小的点。

  2. 由于每个点只能被更新一次,所以在线段树上将该点删除。

  3. 松弛,将点在线段树上更新。

这样,线段树的规模就只和\(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;
}
posted @ 2024-10-04 18:36  lichenyu_ac  阅读(22)  评论(0)    收藏  举报