• 最短路复习


    其实这篇文章曾经在初赛复习里,只是觉得把它拿出来以后可能找的更方便一些。

    - Dijkstra:贪心策略,每次取与源点距离最近的点。一个点不能重复入堆,不适用于负权边(或负环)

    1. 朴素版
    #include<bits/stdc++.h>
    using namespace std;
    #define inf 1e8
    int mark[10005],dis[10005];
    struct node{int to,v;};
    vector<node>edge[10005];
    int main(){
        int n,m,s,t,x,y,z;
        scanf("%d%d%d%d",&n,&m,&s,&t);
        memset(dis,0,sizeof(dis));dis[s]=0;
        for(int i=1;i<=m;i++){
            scanf("%d%d%d",&x,&y,&z);
            edge[x].push_back(node<%y,z%>);
        }
        for(int j=1;j<=n;j++){
    	    int len=inf*2,p;
    	    for(int i=1;i<=n;i++)
    			if(!mark[i]&&dis[i]<len)len=dis[i],p=i;
    	    mark[p]=1;
    	    for(int i=0;i<edge[p].size();i++){
    		    int b=edge[p][i].to;
    		    int c=edge[p][i].v;
    		    if(!mark[b]&&dis[b]>dis[p]+c)
    			    dis[b]=dis[p]+c;
    		}
    	}
        if(dis[t]==inf)cout<<-1;
        else cout<<dis[t];
    }
    

    时间复杂度 (O(n^2))

    2.堆优化:

    #include<bits/stdc++.h>
    using namespace std;
    #define inf 1e8
    struct node{int to,v;};
    bool operator<(node a,node b){return a.v>b.v;}
    priority_queue<node>q;
    vector <node> edge[10005];
    int mark[10005],dis[10005];
    int main(){
    	int n,m,s,t,a,b,c;
    	scanf("%d%d%d%d",&n,&m,&s,&t);
    	for(int i=1;i<=n;i++)dis[i]=inf;
    	for(int i=1;i<=m;i++){
    		scanf("%d%d%d",&a,&b,&c);
    		edge[a].push_back(node<%b,c%>);
    	}
    	dis[s]=0;
    	q.push((node){s,0});
    	while(!q.empty()){
    		node tmp=q.top();q.pop();
    		int k=tmp.to;
    		if(mark[k])continue;
    		mark[k]=1;
    		for(int i=0;i<edge[k].size();i++){
    			int y=edge[k][i].to;
    			int v=edge[k][i].v;
    			if(dis[y]>dis[k]+v){
    				dis[y]=dis[k]+v;
    				q.push((node){y,dis[y]});
    			}
    		}
    	}
    	if(dis[t]==inf)dis[t]=-1;
    	printf("%d",dis[t]);
    }
    

    时间复杂度:(O((m+n)logn))

    以前打堆优化好像有很多都在mark的地方打错了,但数据比较水。

    提供一组hack:

    4 4 1 4
    1 2 1
    1 3 2
    2 4 6
    3 4 2
    
    输出:4
    

    - bellmanford:时间复杂度 (O(n * m)),适用于负权边,可以判负环。

    #include<bits/stdc++.h>
    using namespace std;
    struct node{int from,to,v;}edge[5005];
    int n,m,dis[2505];
    namespace bellmanford{
    	void solve(){
    		for(int i=1;i<=m;i++)
    			scanf("%d%d%d",&edge[i].from,&edge[i].to,&edge[i].v);
    		memset(dis,127,sizeof(dis));
    		int inf=dis[0];
    		dis[1]=0;
    		for(int i=1;i<=n;i++){
    			bool flag=0;
    			for(int j=1;j<=m;j++){
    				int a=edge[j].from;
    				int b=edge[j].to;
    				int c=edge[j].v;
    				if(dis[a]==inf)continue;
    				if(dis[b]>dis[a]+c){
    					if(i<n)dis[b]=dis[a]+c;
    					flag=1;
    				}
    			}
    			if(!flag)break;
    		}
    		if(dis[n]==inf)puts("-1");
    		else cout<<dis[n];
    	}
    }
    int main(){
    	scanf("%d%d",&n,&m);
    	bellmanford::solve();
    }
    

    如果想要判负环的话,就可以把最外面那层循环变成 (2 * n),如果 (i > n)后还有被更新的话就说明存在负环。

    - SPFA: 关于SPFA,他已经死了,一个点可以重复入队,是bellmanford的队列优化,复杂度比较神奇,可能会被卡成 (n * m)

    #include<bits/stdc++.h>
    using namespace std;
    #define inf 1e8
    struct node{int to,v;};
    vector <node> edge[10005];
    int dis[10005],isin[10005],num[10005];
    queue<int>q;
    int main(){
    	int n,m,s,t,a,b,c;
    	scanf("%d%d%d%d",&n,&m,&s,&t);
    	for(int i=1;i<=n;i++)dis[i]=inf;
    	for(int i=1;i<=m;i++){
    		scanf("%d%d%d",&a,&b,&c);
    		edge[a].push_back(node<%b,c%>);
    	}
    	dis[s]=0;
    	q.push(s);isin[s]=1;num[s]=1;
    	while(!q.empty()){
    		int nw=q.front();q.pop();isin[nw]=0;
    		for(int i=0;i<edge[nw].size();i++){
    			int y=edge[nw][i].to,v=edge[nw][i].v;
    			if(dis[y]>dis[nw]+v){
    				dis[y]=dis[nw]+v;
    				if(isin[y])continue;
    				isin[y]=1;
    				q.push(y);
    				num[y]++;
    				if(num[y]>n){puts("有负环");return 0;}
    			}
    		}
    	}
    	if(dis[t]==inf)dis[t]=-1;
    	printf("%d",dis[t]);
    }
    

    记得pop的时候要标记一下它已经不在队列里了。

    - Floyd:最暴力的最短路,时间复杂度(O(n^3)),可以求出任意两个点之间的最短路,也是最短的,代码最好记的。

    #include<bits/stdc++.h>
    using namespace std;
    int n,m;
    int dis[105][105];
    void Floyd(){
    	for(int k=1;k<=n;k++)
    		for(int i=1;i<=n;i++)
    			for(int j=1;j<=n;j++)
    				if(dis[i][j]>dis[i][k]+dis[k][j])
    					dis[i][j]=dis[i][k]+dis[k][j];
    }
    int main(){
    	int a,b,c;
    	scanf("%d%d",&n,&m);
    	memset(dis,0x3f,sizeof(dis));
    	int inf=dis[0][0];
    	for(int i=1;i<=m;i++){
    		scanf("%d%d%d",&a,&b,&c);
    		dis[a][b]=min(dis[a][b],c);
    	}
    	for(int i=1;i<=n;i++)dis[i][i]=0;
    	Floyd();
    	if(dis[1][n]==inf)dis[1][n]=-1;
    	printf("%d",dis[1][n]);
    }
    

    三个循环中,k的含义是用k个节点联通两个点。

  • 相关阅读:
    [JXOI2018]游戏
    UltraISO制作启动盘安装CentOS7
    龙芯、鲲鹏、飞腾等国产平台的浏览器
    浏览器相关性能测试网址
    有没有一种组合字体,中文是宋体,英文是times new roman?
    中国航天科工集团第二研究院706所招聘
    华为云
    面试阿里P6难在哪?(面试难点)
    Linux下配置环境变量—— .bashrc 和 /etc/profile
    Linux 内存 占用较高问题排查
  • 原文地址:https://www.cnblogs.com/tangzhiyang/p/11799382.html
Copyright © 2020-2023  润新知