zoukankan      html  css  js  c++  java
  • 最短路问题

    最短路

    Floyd多源最短路

    基本用法

    for(int i=1;i<=p;i++) 
        for(int j=1;j<=p;j++) dis[i][j]=INF;
    for(int k=1;k<=p;k++)
        for(int i=1;i<=p;i++)
            for(int j=1;j<=p;j++)
                dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
    

    优化版本

    void Floyd_1(){//只适用于无向图的情况,距离矩阵必然是对称的
        for(int k = 1; k <= N; k++){
            for(int i = 1; i <= N; i++){
                int t = A[i][k];
                for(int j = 1; j <= i; j++){
                    A[i][j] = min(A[i][j], t+A[k][j]);
                    A[j][i] = A[i][j];
                }
            }
        }
    }
    

    求可达性矩阵

    for(int k=1;k<=n;k++)//dis初始化为0,有边为1
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                dis[i][j]=dis[i][j]|(dis[i][k]&dis[k][j]);
    

    dijkstra单源最短路

    维护两个集合S和T,S是已经确定最短路的点,T是剩余点,建立最短路数组lowcost[],先将起始点的lowcost设为0,其余点设为INF,每次从lowcost[]中选择最小的加入S,再用这个点去更新其它点的lowcost[],循环n次就可以得到所有点的最短路。

    普通版(适用于稠密图)

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn=105;
    const int INF=1e9;
    int dis[maxn][maxn];
    int lowcost[maxn];
    int vis[maxn];
    void dijkstra(int start,int n){
        fill(lowcost+1,lowcost+1+n,INF);
        fill(vis+1,vis+1+n,0);
        lowcost[start]=0;
        for(int i=1;i<=n;i++){
            int minp=-1,minn=INF;
            for(int j=1;j<=n;j++){
                if(!vis[j]&&lowcost[j]<minn){
                    minp=j;
                    minn=lowcost[j];
                }
            }
            if(minp==-1)
                break;
            vis[minp]=1;
            for(int j=1;j<=n;j++){
                if(lowcost[minp]+dis[minp][j]<lowcost[j]){
                    lowcost[j]=lowcost[minp]+dis[minp][j];
                }
            }
        }
    }
    int main () {
        int n,m,s;
        while(scanf("%d%d",&n,&m)){
            if(n==0 && m==0) return 0;
            s=1;
            for(int i=1;i<=n;i++){
                for(int j=1;j<=n;j++){
                    if(i!=j)
                        dis[i][j]=INF;
                }
            }
            for(int i=1;i<=m;i++){
                int u,v,w;
                scanf("%d%d%d",&u,&v,&w);
                dis[v][u]=dis[u][v]=min(dis[u][v],w);
            }
            dijkstra(s,n);
            printf("%d
    ",lowcost[n]);
        }
    }
    

    邻接表+堆优化版(适用于稀疏图)

    同邻接矩阵的区别在于,每次有新的点加入集合后,不需要遍历所有的点,而是将相邻的点存入优先队列,队列中队首的点永远是距离源点最近的点,直接拿出判断是否未加入集合即可。分析得每条边都要插入一次点,插入的复杂度为O(logE),因此整体复杂度为(O(E * logE))

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const ll INF=1e18;
    const int maxn=1e5+5;
    const int maxm=2e5+5;
    struct node{
    	int id;ll cost;
    	bool operator<(const node &b)const{
    		return cost>b.cost;//将小的放上面
    	}
    };
    struct edge{
    	int v;ll cost;
    };
    vector<edge>E[maxm];
    ll lowcost[maxn];
    bool vis[maxn];
    void dijkstra(int n,int start){
    	fill(vis+1,vis+1+n,0);
    	fill(lowcost+1,lowcost+1+n,INF);
    	priority_queue<node>q;
    	lowcost[start]=0; q.push({start,0});
    	while(!q.empty() ){
    		int u=q.top().id;
    		q.pop();
    		if(vis[u])continue;
    		vis[u]=1;
    		for(int i=0;i<E[u].size();i++){
    			int v=E[u][i].v;
    			ll cost=E[u][i].cost;
    			if(!vis[v]&&lowcost[v]>lowcost[u]+cost){
    				lowcost[v]=lowcost[u]+cost;
    				q.push({v,lowcost[v]});
    			}
    		}
    	}
    }
    void addedge(int u,int v,int w){
    	E[u].push_back({v,w});
    }
    int main(){
    	int n,m,s;
    	scanf("%d%d%d",&n,&m,&s);
    	for(int i=1;i<=m;i++){
            int u,v;ll w;
    		scanf("%d%d%lld",&u,&v,&w);
    		addedge(u,v,w);
    	}
    	dijkstra(n,s);
    	for(int i=1;i<=n;i++)
    		printf("%lld ",lowcost[i]);
    }
    

    Bellman_Ford 单源负权边最短路

    以下操作循环执行至多 n-1 次, n 为顶点数:

    • 对于每一条边E[u,v,w]进行松弛,如果dis[v]>dis[u]+w,更新dis[v]。如果在一个循环中没有一个顶点被更新,那么所有顶点的最短路已经更新完毕。

    在n-1次之后再进行一次所有边的松弛,如果有发生更新,说明存在负权边

    复杂度为(V*E)

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const ll INF=1e17;
    const int maxn=1e5+5;
    const int maxm=1e6+5;
    ll dis[maxn],tot=0;
    struct edge{
    	ll u,v,w;
    }E[maxm];
    void addedge(int u,int v,int w){
    	E[++tot].u=u;
    	E[tot].v=v;
    	E[tot].w=w;
    }
    int n,m;
    int bellman_ford(int n,int s){
    	fill(dis+1,dis+1+n,INF);
    	dis[s]=0;
    	for(int i=1;i<=n-1;i++){//最多n-1次
    		int flag=0;
    		for(int j=1;j<=tot;j++)
    			if(dis[E[j].v]>dis[E[j].u]+E[j].w){
    				flag=1;
    				dis[E[j].v]=dis[E[j].u]+E[j].w;
    			}
    		if(!flag) return 1;//未发生松弛,已经更新完毕
    	}
    	for(int j=1;j<=tot;j++){
    		if(dis[E[j].v]>dis[E[j].u]+E[j].w)
    			return 0;
    	}
    	return 1;
    }
    int main(){
        int n,m,s;
        cin>>n>>m>>s;
        for(int i=1;i<=m;i++){
            ll u,v,w;
            scanf("%lld%lld%lld",&u,&v,&w);
            addedge(u,v,w);
        }
        bellman_ford(n,s);
        for(int i=1;i<=n;i++){
            printf("%lld ",dis[i]!=INF?dis[i]:2147483647);
        }
    }
    

    SFPA 单源负权边最短路

    用dis数组记录源点到有向图上任意一点距离,其中源点到自身距离为0,到其他点距离为INF。将源点入队,并重复以下步骤:

    1. 队首x出队
    2. 遍历所有以队首为起点的有向边(x,i),若dis[x]+w(x,i)<dis[i],则更新dis[i]
    3. 如果点i不在队列中,则i入队
    4. 若队列为空,跳出循环;否则执行1

    平均复杂度不确定,极限复杂度为O(VE),但是大多数时候比bellman_ford快

    SLF优化:进队操作:进之前判一下和队首的dis[],若小于队头就直接插在队头。

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int maxn=1e4+5;
    const int maxm=1e6+5;
    const int INF=2e9;
    struct edge{
        int v,w,next;
    }E[maxm];
    int tot=0,head[maxn];
    void addedge(int u,int v,int w){
        E[++tot].v=v;
        E[tot].w=w;
        E[tot].next=head[u];
        head[u]=tot;
    }
    int dis[maxn],vis[maxn];
    int q[maxn];
    int n,m,s;
    void spfa(){
        fill(dis,dis+n+1,INF);
        fill(vis,vis+n+1,0);
        int l=0,r=1;
        q[1]=s;
        dis[s]=0;
        vis[s]=1;
        while(l!=r){
            int u=q[l=l==n?0:l+1];
            vis[u]=0;
            for(int i=head[u];i;i=E[i].next){
                int v=E[i].v;
                int w=E[i].w;
                if(dis[u]+w<dis[v]){
                    dis[v]=dis[u]+w;
                    if(!vis[v]){
                        if(dis[v]>dis[l+1])q[r=r==n?0:r+1]=v;
                        else q[l]=v,l=l==0?n:l-1;
                        vis[v]=1;
                    }
                }
            }
        }
    }
    int main(){
        cin>>n>>m>>s;
        for(int i=1;i<=m;i++){
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            addedge(u,v,w);
        }
        spfa();
        for(int i=1;i<=n;i++){
            printf("%d ",dis[i]!=INF?dis[i]:2147483647);
        }
    }
    

    例题

    Cow Contest(Floyd)

    https://www.luogu.org/problem/P2419

    题意:N个人,能力各不相同,两两比赛能力强的获胜,进行了M场比赛,给出比赛结果,问知道确切排名的人有几个?

    思路:一个人若能与其他所有人的关系都确定,那么他的排名也是确定的。将能力关系转化为有向图,A战胜了B,就建立一条A指向B的边。如何判断两个人关系是否确定?只要从任意一方到另一方是可达的,那么他们的关系就是确定的。利用floyd求可达性矩阵,然后(n^2)枚举即可得出答案

    #include <iostream>
    using namespace std;
    const int maxn=505;
    int dis[maxn][maxn];
    int a[maxn];
    int main(){
    	int n,m;
    	cin>>n>>m;
    	for(int i=1;i<=m;i++){
    		int u,v;
    		cin>>u>>v;
    		dis[u][v]=1;
    	}
    	for(int k=1;k<=n;k++)
    		for(int i=1;i<=n;i++)
    			for(int j=1;j<=n;j++)
    				dis[i][j]=dis[i][j]|(dis[i][k]&dis[k][j]);
    	int ans=0;
    	for(int i=1;i<=n;i++){
    		int ok=1;
    		for(int j=1;j<=n;j++){
    			if(i==j)continue;
    			if(!dis[i][j]&&!dis[j][i]){
    				ok=0;
    				break;
    			}
    		}
    		if(ok)ans++;
    	}
    	cout<<ans<<endl;
    }
    

    灾后重建(Floyd)

    https://www.luogu.org/problem/P1119

    题意:N个点,M条边(带权),每个点i在t[i]时间时才会加入图中,多个询问,问t时刻u,v两点的最短路

    思路:按照Floyd的方法,将点与询问都按时间从小到大排序,通过当前询问时间点前已存在(新加入)的点逐个进行松弛操作

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    using namespace std;
    const int maxn=205;
    const int maxq=5e4+5;
    const int INF=0x3f3f3f3f;
    int dis[maxn][maxn];
    struct node{
    	int id,t;
    }a[maxn];
    struct Q{
    	int u,v,t,id;
    }query[maxq];
    int ans[maxq];
    bool cmp1(node a,node b){
    	return a.t<b.t;
    }
    bool cmp2(Q a,Q b){
    	return  a.t<b.t;
    }
    int ok[maxn];
    int main(){
    	int n,m;
    	cin>>n>>m;
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=n;j++)
    			dis[i][j]=INF;
    	for(int i=1;i<=n;i++){
    		cin>>a[i].t;
    		a[i].id=i;
    	}
    	for(int i=1;i<=m;i++){
    		int u,v,w;
    		scanf("%d%d%d",&u,&v,&w);
    		u++;v++;
    		dis[u][v]=dis[v][u]=w;
    	}
    	sort(a+1,a+1+n,cmp1);
    	int q;
    	cin>>q;
    	for(int i=1;i<=q;i++){
    		cin>>query[i].u>>query[i].v>>query[i].t;
    		query[i].u++;query[i].v++;
    		query[i].id=i;
    	}
    	sort(query+1,query+1+q,cmp2);
    	int p=1;
    	for(int i=1;i<=q;i++){
    		while(p<=n && query[i].t>=a[p].t){
    			int v=a[p].id;
    			ok[v]=1;
    			for(int j=1;j<=n;j++)
    				for(int k=1;k<=n;k++)
    					dis[j][k]=min(dis[j][k],dis[j][v]+dis[v][k]);
    			p++;
    		}
    		int &u=query[i].u,&v=query[i].v,&id=query[i].id;
    		if(ok[u] && ok[v] && dis[u][v]!=INF)
    			ans[id]=dis[u][v];
    		else ans[id]=-1;
    	}
    	for(int i=1;i<=q;i++)
    		printf("%d
    ",ans[i]);
    }
    

    负环(SPFA)

    https://www.luogu.org/problem/P3385

    给定一个有向图,判断是否存在从1能到达额负环

    可以用bellman_ford解,将与1连通的点和边重现建图,跑一遍bellman_ford即可(有超时风险)。

    也可以用spfa从1开始跑,记录每个点的松弛次数,如果>=n,则存在负环

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn=1e4+5;;
    const int maxm=1e6+5;
    const int INF=2e9;
    struct edge {
        int v,w,next;
    } E[maxm];
    int tot=0,head[maxn];
    void addedge(int u,int v,int w) {
        E[++tot].v=v;
        E[tot].w=w;
        E[tot].next=head[u];
        head[u]=tot;
    }
    int dis[maxn];
    int vis[maxn],cnt[maxn];
    int t,n,m;
    bool spfa(int s,int n) {
        fill(dis,dis+n+1,INF);
        fill(vis,vis+n+1,0);
        fill(cnt,cnt+n+1,0);
        queue<int>q;
        q.push(s);
        vis[s]=1;
        dis[s]=0;
        while(!q.empty()) {
            int u=q.front();
            q.pop();
            vis[u]=0;
            if(cnt[u]>=n)
                return 1;
            for(int i=head[u]; i; i=E[i].next) {
                int v=E[i].v,w=E[i].w;
                if(dis[u]+w<dis[v]) {
                    dis[v]=dis[u]+w;
                    if(!vis[v]) {
                        q.push(v);
                        vis[v]=1;
                        cnt[v]++;
                        if(cnt[v]>=n)
                            return 1;
                    }
                }
            }
        }
        return 0;
    }
    int main() {
        cin>>t;
        while(t--) {
            cin>>n>>m;
            memset(head,0,sizeof(head));
            tot=0;
            for(int i=1; i<=m; i++) {
                int u,v,w;
                scanf("%d%d%d",&u,&v,&w);
                addedge(u,v,w);
                if(w>=0)
                    addedge(v,u,w);
            }
            if(spfa(1,n)) {
                printf("YE5
    ");
            } else {
                printf("N0
    ");
            }
        }
    }
    
    

    P1144 最短路计数(dijkstra)

    给定一个无向图,边权均为1,问每个点到其他点的最短路条数,答案对100003取模

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const ll INF=1e18;
    const int mod=100003;
    const int maxn=1e6+5;
    const int maxm=2e6+5;
    struct node{
    	int id;ll cost;
    	bool operator<(const node &b)const{
    		return cost>b.cost;//将小的放上面
    	}
    };
    struct edge{
    	int v;ll cost;
    };
    vector<edge>E[maxm];
    ll lowcost[maxn];
    int cnt[maxn];
    bool vis[maxn];
    void dijkstra(int n,int start){
    	fill(vis+1,vis+1+n,0);
    	fill(lowcost+1,lowcost+1+n,INF);
    	priority_queue<node>q;
    	lowcost[start]=0; q.push({start,0});
    	cnt[start]=1;
    	while(!q.empty() ){
    		int u=q.top().id;
    		q.pop();
    		if(vis[u])continue;
    		vis[u]=1;
    		for(int i=0;i<E[u].size();i++){
    			int v=E[u][i].v;
    			ll cost=E[u][i].cost;
    			if(!vis[v]){
                    if(lowcost[v]>lowcost[u]+cost){
                        lowcost[v]=lowcost[u]+cost;
                        q.push({v,lowcost[v]});
                        cnt[v]=cnt[u];
                    }
                    else if(lowcost[v]==lowcost[u]+cost){
                        cnt[v]=(cnt[v]+cnt[u])%mod;
                    }
    			}
    		}
    	}
    }
    void addedge(int u,int v,int w){
    	E[u].push_back({v,w});
    }
    int main(){
    	int n,m,s;
    	scanf("%d%d",&n,&m);
    	s=1;
    	for(int i=1;i<=m;i++){
            int u,v;
    		scanf("%d%d",&u,&v);
    		addedge(u,v,1);
    		addedge(v,u,1);
    	}
    	dijkstra(n,s);
    	for(int i=1;i<=n;i++)
    		printf("%d
    ",cnt[i]);
    }
    
  • 相关阅读:
    C++之栈的应用-------判断出栈序列是否合法
    PHP 使用soap调用webservice接口
    C++矩阵转置
    C++利用栈实现队列、利用队列实现栈
    C++ error LNK2019: 无法解析的外部符号
    定时任务
    spring boot redis CacheManager
    非正常关闭vim编辑器后提示错误的解决方法
    python pandas学习记录 二
    layui点击放大图片
  • 原文地址:https://www.cnblogs.com/ucprer/p/11389722.html
Copyright © 2011-2022 走看看