SPFA

spfa最短路算法,可以判断负环,平均时间复杂度O(kE),k<=2,被卡时间复杂度O(VE)

inline void spfa(int s){/*以s为源点的单源最短路*/
    queue<int>q;
    memset(in,0,sizeof(in));/*清空标记*/
    memset(dis,0x3f,sizeof(dis));/*初始化正无穷*/
    dis[s]=0;/*源点距离为0*/
    q.push(s);/*源点入队*/
    while(!q.empty()){
        int x=q.front();
        q.pop();
        in[x]=0;/*x不在队中*/
        for(int i=h[x];i;i=e[i].next){
            int y=e[i].to;
            if(dis[y]>dis[x]+e[i].w){/*松弛操作*/
                dis[y]=dis[x]+e[i].w;
                cnt[y]=cnt[x]+1;/*继承经过边数,cnt>=n时则为存在负环*/
                pre[y]=x;/*记录路径*/
                if(!in[y]){/*不在队中*/
                    in[y]=1;/*标记*/
                    q.push(y);/*入队*/
                }
            }
        }
    }
}

SLF优化,使用双端队列,每次进队元素跟队首比较,若dis小于队首则插入队首,否则插入队尾。

inline void spfa(int s){
    deque<int>q;
    memset(in,0,sizeof(in));
    memset(dis,0x3f,sizeof(dis));
    q.push_front(s);
    dis[s]=0;
    while(!q.empty()){
        int x=q.front();
        q.pop_front();
        in[x]=0;
        for(int i=h[x];i;i=e[i].next){
            int y=e[i].to;
            if(dis[y]>dis[x]+e[i].w){
                dis[y]=dis[x]+e[i].w;
                if(!in[y]){
                    in[y]=1;
                    if(q.empty()/*队列为空放直接入队*/||dis[y]<dis[q.front()]/*小于队首从队首入队*/)q.push_front(y);
                    else q.push_back(y);/*否则从队尾入队*/
                }
            }
        }
    }
}

LLL优化,每次队首元素与队中元素平均值进行比较,若大于平均值则将x移至队尾,再取队首元素。

inline void spfa(int s){
    deque<int>q;
    memset(in,0,sizeof(in));
    memset(dis,0x3f,sizeof(dis));
    q.push_front(s);
    dis[s]=0;
    int tot=1,sum=0;
    while(!q.empty()){
        int x=q.front();
        while(tot*dis[x]>sum){/*大于平均值*/
            q.pop_front();
            q.push_back(x);/*移至队尾*/
            x=q.front();/*再取队首*/
        }
        q.pop_front();
        in[x]=0;
        tot--;/*元素个数减少*/
        sum-=dis[x];/*总和减少*/
        for(int i=h[x];i;i=e[i].next){
            int y=e[i].to;
            if(dis[y]>dis[x]+e[i].w){
                dis[y]=dis[x]+e[i].w;
                if(!in[y]){
                    in[y]=1;
                    if(q.empty()||dis[y]<dis[q.front()])q.push_front(y);
                    else q.push_back(y);
                    tot++;/*增加元素个数*/
                    sum+=dis[y];/*增加总和*/
                }
            }
        }
    }
}

随机化spfa,手写双端队列+rand+sort,注意队列范围和l非负。

inline void spfa(int s){
    int q[N*100],l=1,r=0;
    memset(in,0,sizeof(in));
    memset(dis,0x3f,sizeof(dis));
    q[++r]=s;
    dis[s]=0;
	while(l<=r){
		int x=q[l++];
		in[x]=0;
		if(rand()%((r-l+1>>3)+1)==0)sort(q+l,q+r+1,[](int x,int y){return dis[x]<dis[y];});
		for(int i=h[x];i;i=e[i].next){
			int y=e[i].to;
			if(dis[y]>dis[x]+e[i].w){
				dis[y]=dis[x]+e[i].w;
				if(!in[y]){
					in[y]=1;
					if(l>1)q[--l]=y;
					else q[++r]=y;
				}
			}
		}
	}
}

随机化spfa,手写双端队列+rand,注意队列范围和l非负。

inline void spfa(int s){
    int q[N*100],l=1,r=0;
    memset(in,0,sizeof(in));
    memset(dis,0x3f,sizeof(dis));
    q[++r]=s;
    dis[s]=0;
	while(l<=r){
		int x=q[l++];
		in[x]=0;
		for(int i=h[x];i;i=e[i].next){
			int y=e[i].to;
			if(dis[y]>dis[x]+e[i].w){
				dis[y]=dis[x]+e[i].w;
				if(!in[y]){
					in[y]=1;
					if(rand()&1&&l>1)q[--l]=y;
					else q[++r]=y;
				}
			}
		}
	}
}

最短路计数,对于边x->y,若可以对y进行松弛,则继承x的次数,若恰好边权相等,则累加x的次数。

    f[s]=1;
            if(dis[y]>dis[x]+e[i].w){
                dis[y]=dis[x]+e[i].w;
                f[y]=f[x];
                if(!in[y])in[y]=1,q.push(y);
            }
            else if(dis[y]==dis[x]+e[i].w)(f[y]+=f[x])%=mod;

计算1到其他点的最短距离和其他点到1的最短距离的距离和。前者正图后者建反图。

signed main(){
    cin>>n>>m;
    for(int i=1;i<=m;i++)cin>>a[i]>>b[i]>>c[i],add(a[i],b[i],c[i]);
    spfa(1);memset(h,0,sizeof(h));tot=0;
    for(int i=2;i<=n;i++)ans+=dis[i];
    for(int i=1;i<=m;i++)add(b[i],a[i],c[i]);spfa(1);
    for(int i=2;i<=n;i++)ans+=dis[i];
    cout<<ans;
    return 0;
}

一张图中选一个节点使得其他点到该店的费用和最小,费用为距离乘点权。枚举每个点进行spfa。

    for(int i=1;i<=n;i++){
        spfa(i);
        int re=0;
        for(int j=1;j<=n;j++)re+=dis[j]*p[j];
        ans=min(ans,re);
    }

从s到t的经过的边权最大值最小。将松弛操作的和判断改为最大值判断即可。

            if(dis[y]>max(dis[x],e[i].w)){
                dis[y]=max(dis[x],e[i].w);
                if(!in[y]){
                    in[y]=1;
                    q.push(y);
                }
            }

x转账给y需要z%的手续费,求s到t的最少总费用。转换成小数后用1去减,松弛时用乘法判断。

    dis[s]=1;
            if(dis[y]<dis[x]*e[i].w){
                dis[y]=dis[x]*e[i].w;
                if(!in[y]){
                    in[y]=1;
                    q.push(y);
                }
            }
    for(int i=1;i<=m;i++){
        int a,b,c;cin>>a>>b>>c;
        add(a,b,1-1.0*c/100);
        add(b,a,1-1.0*c/100);
    }
    printf("%.8lf",100/dis[t]);

问哪条路断了之后将会存在某像个城市无法连通。枚举每条边进行spfa,无法到达则计入答案。

    int k=0;
    for(int i=1;i<=m;i++){
        spfa(a[i],b[i]);
        if(dis[b[i]]>=(int)1e7)p[++k]={min(a[i],b[i]),max(a[i],b[i])};
    }

二维迷宫,#不可通过,.可简单通过,A->Z为传送装置,=出口,@起点,每次移动一格话费一单位时间,从装置的一个结点到另一个结点不花时间。将二维平面拆点乘一维,对于传送门,有两个门的传到另一个结点即可。

    for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){
        int ad=i*m+j;
        if(mp[i][j]=='@')s=ad;
        if(mp[i][j]=='=')t=ad;
        if('A'<=mp[i][j]&&mp[i][j]<='Z'){
            int c=mp[i][j]-'A'+1;
            cs[c][++f[c]]=ad;
        }
    }
    for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){
        if(mp[i][j]!='#'){
            int ad=i*m+j;
            for(int k=1;k<5;k++){
                int tx=i+dx[k],ty=j+dy[k];
                if(tx<1||ty<1||tx>n||ty>m||mp[tx][ty]=='#')continue;
                if('A'<=mp[tx][ty]&&mp[tx][ty]<='Z'&&f[mp[tx][ty]-'A'+1]==2){
                    int to=tx*m+ty;
                    if(to==cs[mp[tx][ty]-'A'+1][1])to=cs[mp[tx][ty]-'A'+1][2];
                    else to=cs[mp[tx][ty]-'A'+1][1];
                    add(ad,to,1);
                }
                else add(ad,tx*m+ty,1);
            }
        }
    }

n*n平面第i行有线段,左端点(i,l[i]),右端点(i,r[i]),从(1,1)到(n,n),不可向上走,必须走完本行的线段才能向下走。以上一行的点为起点,到当前行的点的距离为上一行的点到当前行另外一点的距离加上当前行的长度,建新点。

    m=1;
    for(int i=1;i<=n;i++){
        cin>>a[i]>>b[i];
        m++;
        if(i==1)add(1,m,(b[i]<<1)-a[i]-1);
        else add(m-2,m,abs(b[i]-a[i-1])+b[i]-a[i]),add(m-1,m,abs(b[i]-b[i-1])+b[i]-a[i]);
        m++;
        if(i==1)add(1,m,b[i]-1);
        else add(m-3,m,abs(a[i]-a[i-1])+b[i]-a[i]),add(m-2,m,abs(a[i]-b[i-1])+b[i]-a[i]);
    }
    m++;
    add(m-2,m,n-a[n]),add(m-1,m,n-b[n]);
    spfa(1);
    cout<<dis[m]+n-1;

一部分道路损坏,求s到t要修复道路的总长度最小值。损坏的道路正常连边,其余道路边权为0.

    for(int i=1;i<=d;i++){
        int s,t;
        cin>>s>>t;
        vis[mp[{s,t}]]=vis[mp[{t,s}]]=1;
    }
    for(int i=1;i<=tot;i++)if(!vis[i])e[i].w=0;

无向连通图中,设包含在x-y最短路径上的顶点集合为I(x,y),询问若干点对x,y,求I(x,y)。分别以x和y作为源点进行最短路,枚举每个起点,若x到i的最短路+t到i的最短路=s到t的最多路,则i一定在s到t的最短路上。

        spfa(s);
        int ans[N];
        for(int i=1;i<=n;i++)ans[i]=dis[i];
        spfa(t);
        for(int i=1;i<=n;i++)if(ans[i]+dis[i]==dis[s])cout<<i<<' ';

无向连通图,定义简单路径为路径上所有边边权异或和,不存在简单环使得边权异或和不为0,多次询问x到y的最短简单路径。所有环的异或和为0,即最短路上的环不会影响路径长度,x-y的简单路径即1-x的简单路径异或1-y的简单路径。

            if(dis[y]>(dis[x]^e[i].w)){
                dis[y]=(dis[x]^e[i].w);
                if(!in[y]){
                    in[y]=1;
                    q.push(y);
                }
            }
posted @ 2022-11-14 17:42  半步蒟蒻  阅读(104)  评论(0)    收藏  举报