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]);
    }
    
  • 相关阅读:
    Study Plan The Twelfth Day
    Study Plan The Fifteenth Day
    Study Plan The Seventeenth Day
    Study Plan The Tenth Day
    Study Plan The Eighth Day
    Study Plan The Eleventh Day
    Study Plan The Sixteenth Day
    Study Plan The Thirteenth Day
    Study Plan The Fourteenth Day
    Study Plan The Ninth Day
  • 原文地址:https://www.cnblogs.com/ucprer/p/11389722.html
Copyright © 2011-2022 走看看