【算法竞赛入门到进阶】10.9最短路

结点n、边m边权值选用算法数据结构
n < 200 n<200 n<200允许有负Floyd邻接矩阵
n ∗ m < 1 0 7 n*m<10^7 nm<107允许有负Bellman-Ford邻接表
更大有负SPFA邻接表、前向星
更大无负数Dijkstra邻接表、前向星

【例:HDU2544 最短路径】

1.Floyd: O ( n 3 ) O(n^3) O(n3)

  能够一次性解决所有结点之间的最短路;能够处理负圈。

#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
using namespace std;
int n,m;
const int INF=0x3f3f3f3f;
int graph[210][210];
void floyd()
{
    for(int k=1;k<=n;++k) //中转节点要定义在外层循环
        for(int i=1;i<=n;++i)
            if(graph[i][k]!=INF)
            for(int j=1;j<=n;++j)
                if(graph[i][j]>graph[i][k]+graph[k][j]) graph[i][j]=graph[i][k]+graph[k][j];
    cout<<graph[1][n]<<endl;
}
int main()
{
    close;
    while(cin>>n>>m)
    {
        if(n==0 && m==0) break;
        for(int i=1;i<=n;++i)
            for(int j=1;j<=n;++j)
                graph[i][j]=INF;
        for(int i=1;i<=m;++i)
        {
            int x,y,cost;cin>>x>>y>>cost;
            graph[x][y]=graph[y][x]=cost;
        }
        floyd();
    }
}

2.Bellman-Ford: O ( n m ) O(nm) O(nm)

  适用于给定起点,求出到其他所有点之间的距离。每一次更新都是一次松弛,至少能找到一个新的结点的最短路径,因此算法会趋于结束。然后注意存图的时候不要采用邻接矩阵,因为每一轮我们都要检查所有的边,采用邻接矩阵会使每轮检查的边的数量都变成 n 2 n^2 n2;Bellman-Ford能用来处理负圈的情况。

#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
using namespace std;
const int maxn=1e4+100;
const int INF=0x3f3f3f3f;
struct Edge{
    int u,v,w;
}e[maxn];
int n,m,cnt,pre[maxn];
void Bellman_Ford()
{
    int s=1;
    int d[maxn];
    for(int i=1;i<=n;++i) d[i]=INF;
    d[s]=0;
    for(int k=1;k<=n;++k)
        for(int i=0;i<cnt;++i)
        {
            int x=e[i].u,y=e[i].v;
            if(d[x]>d[y]+e[i].w) d[x]=d[y]+e[i].w;
            pre[x]=y;
        }
    cout<<d[n]<<endl;
}
int main()
{
    close;
    while(cin>>n>>m)
    {
        if(n==0 && m==0) break;
        cnt=0;
        for(int i=1;i<=m;++i)
        {
            int x,y,cost;cin>>x>>y>>cost;
            e[cnt].u=x;e[cnt].v=y;e[cnt++].w=cost;
            e[cnt].u=y;e[cnt].v=x;e[cnt++].w=cost;
        }
        Bellman_Ford();
    }
}

3.SPFA

  SPFA的特点是每次都对一个结点u的所有有状态更新的结点入队,因为更新的是当前最短的距离,所以可能会造成某一个结点出队后又再次入队更新距离。这个算法停止更新是当队列为空时算法结束,效率取决于每次进队的点的数量,因此SPFA是不稳定的!

#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
using namespace std;
const int maxn=1e4+100;
const int INF=0x3f3f3f3f;
struct Edge{
    int to,next,w;
}e[maxn];
int n,m,cnt,head[maxn],dis[maxn],Neg[maxn],pre[maxn];
bool inq[maxn];
void init()
{
    for(int i=0;i<maxn;++i)
        head[i]=-1,e[i].next=-1;
    cnt=0;
}
void addedge(int x,int y,int cost)
{
    e[cnt].to=y;e[cnt].w=cost;
    e[cnt].next=head[x];
    head[x]=cnt++;
}
int SPFA(int s)
{
    memset(Neg,0,sizeof(Neg));
    Neg[s]=1;
    for(int i=1;i<=n;++i) {dis[i]=INF;inq[i]=false;}
    dis[s]=0;
    queue<int> Q;
    Q.push(s);inq[s]=true;
    while(!Q.empty())
    {
        int u=Q.front();Q.pop();
        inq[u]=false;
        for(int i=head[u];~i;i=e[i].next)
        {
            int v=e[i].to,w=e[i].w;
            if(dis[u]+w<dis[v]){
                dis[v]=dis[u]+w;
                pre[v]=u;
                if(!inq[v]){
                    inq[v]=true;
                    Q.push(v);
                    Neg[v]++;
                    if(Neg[v]>n) return 1;
                }
            }
        }
    }
    cout<<dis[n]<<endl;
}
int main()
{
    close;
    while(cin>>n>>m)
    {
        if(n==0 && m==0) break;
        init();
        for(int i=1;i<=m;++i)
        {
            int x,y,cost;cin>>x>>y>>cost;
            addedge(x,y,cost);
            addedge(y,x,cost);
        }
        SPFA(1);
    }
}

4.Dijkstra: O ( m l o g n ) O(mlogn) O(mlogn)

  贪心的思想,每次都在找最短距离。

#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
using namespace std;
const int maxn=1e4+100;
const int INF=0x3f3f3f3f;
struct Edge{
    int from,to,w;
    Edge(int a,int b,int c){from=a;to=b;w=c;}
};
vector<Edge> e[maxn];
struct s_node{
    int id,n_dis;
    s_node(int b,int c){id=b;n_dis=c;}
    bool operator <(const s_node &a)const{
        return n_dis>a.n_dis;
    }
};
int n,m;
int pre[maxn];
void Dijkstra(int s,int f)
{
    int dis[maxn];bool done[maxn];
    for(int i=1;i<=n;++i) {dis[i]=INF;done[i]=false;}
    dis[s]=0;
    priority_queue<s_node> Q;
    Q.push(s_node(s,dis[s]));
    while(!Q.empty())
    {
        s_node u=Q.top();Q.pop();
        if(done[u.id]) continue;
        done[u.id]=true;
        for(int i=0;i<e[u.id].size();++i)
        {
            Edge y=e[u.id][i];
            if(done[y.to]) continue;
            if(dis[y.to]>y.w+u.n_dis){
                dis[y.to]=y.w+u.n_dis;
                Q.push(s_node(y.to,dis[y.to]));
                pre[y.to]=u.id;
            }
        }
    }
    cout<<dis[n]<<endl;
}
int main()
{
    close;
    while(cin>>n>>m)
    {
        if(n==0 && m==0) break;
        for(int i=1;i<=n;++i) e[i].clear();
        while(m--)
        {
            int x,y,cost;cin>>x>>y>>cost;
            e[x].push_back(Edge(x,y,cost));e[y].push_back(Edge(y,x,cost));
        }
        Dijkstra(1,n);
    }
}

Practice:

1.HDU 1385 Minimum Transport Cost

  点数少,而且要求求解的是任意两点之间的最短距离,考虑Floyd做法。题目同时要求的是打印路径,这里可以定义 p a t h [ i ] [ j ] path[i][j] path[i][j]表示从点 i i i出发的下一个结点,初始情况下 p a t h [ i ] [ j ] = j path[i][j]=j path[i][j]=j(初始化时的答案都是两个点之间连了一条边)。然后注意在每一次的松弛操作时,都要加上结点本身的值。

#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
using namespace std;
int n;
const int INF=0x3f3f3f3f;
int graph[210][210],tax[210],path[210][210];
void floyd()
{
    for(int k=1;k<=n;++k) //中转节点要定义在外层循环
        for(int i=1;i<=n;++i)
            if(graph[i][k]!=INF)
            for(int j=1;j<=n;++j)
                if(graph[i][j]>graph[i][k]+graph[k][j]+tax[k]) graph[i][j]=graph[i][k]+graph[k][j]+tax[k],path[i][j]=path[i][k];
                else if(graph[i][j]==graph[i][k]+graph[k][j]+tax[k] && path[i][j]>path[i][k]) path[i][j]=path[i][k];
}
void Print_Path(int s,int t)
{
    if(s==t) {printf("%d",s);return;}
    printf("%d-->",s);
    Print_Path(path[s][t],t);
}
int main()
{
    close;
    while(cin>>n)
    {
        if(n==0) break;
        for(int i=1;i<=n;++i)
            for(int j=1;j<=n;++j)
            {
                cin>>graph[i][j];
                if(graph[i][j]==-1) graph[i][j]=INF;
                path[i][j]=j;
            }   
        for(int i=1;i<=n;++i) cin>>tax[i];
        floyd();
        int s,t;
        while(cin>>s>>t)
        {
            if(s==-1 && t==-1) break;
            printf("From %d to %d :\n",s,t);
            printf("Path: ");Print_Path(s,t);printf("\n");
            printf("Total cost : %d\n\n",graph[s][t]);
        }
    }
}

2.HDU 1599 find the mincost route

※Floyd求解最小环问题
  首先明白一下Floyd的原理,对于当前中转点为 k k k的时候,当前图中任意两个点的最短路,他们要不然是可以直达(有一条边),要不通过中转点,而且这个中转点的值一定比 k k k小。
  现在我们要求解最少包括三个不同结点的环,那么我们不妨令这个环经过了结点 k k k,然后我们在 1... n 1...n 1...n找两个不同的结点 a , b a,b a,b,满足两个结点与结点 k k k有边相连。我们让 m i n c i r c l e mincircle mincircle表示环的最小长度,当 m i n c i r c l e > d i s t [ a ] [ b ] + g r a p h [ k ] [ a ] + g r a p h [ k ] [ b ] mincircle>dist[a][b]+graph[k][a]+graph[k][b] mincircle>dist[a][b]+graph[k][a]+graph[k][b]时,更新答案。
  最后一定要注意一下对于重边的处理,因为存储的方式是邻接矩阵,我们保存的边权一定要是重边中最小的边权。

#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
using namespace std;
int n,m;
const int INF=0x3f3f3f3f;
int graph[210][210],dist[210][210],path[210][210],mincircle;
void floyd()
{
    for(int k=1;k<=n;++k) 
    {
        for(int i=1;i<=n;++i)
        if(graph[i][k]!=INF)
            for(int j=i+1;j<=n;++j)
                if(mincircle>graph[i][k]+graph[j][k]+dist[i][j])  mincircle=graph[i][k]+graph[j][k]+dist[i][j];
        for(int i=1;i<=n;++i)
            if(dist[i][k]!=INF)
            for(int j=1;j<=n;++j)
                if(dist[i][j]>dist[i][k]+dist[k][j]) dist[i][j]=dist[i][k]+dist[k][j];
    }    
}
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        for(int i=1;i<=n;++i)
            for(int j=1;j<=n;++j)
                graph[i][j]=dist[i][j]=INF;
        for(int i=0;i<m;++i)
        {
            int x,y,cost;scanf("%d%d%d",&x,&y,&cost);
            graph[x][y]=graph[y][x]=dist[x][y]=dist[y][x]=min(cost,graph[x][y]);
        }
        mincircle=INF;
        floyd();
        if(mincircle!=INF) printf("%d\n",mincircle);
        else printf("It's impossible.\n");
    }
}

3.HDU 3631 Shortest Path

  这个题仍然是多组求解有向图中两个点之间的最短距离,但变化是最短路径必须经过的点必须是经过标记的。这要理解floyd的根本含义:中转点为 k k k时的最短距离。因此一个点被标记不仅仅意味着这个点可以经过,更重要的是可以把这点作为中转点

#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
using namespace std;
int n,m,Q;
const int INF=0x3f3f3f3f;
int graph[400][400];
bool vis[400];
void floyd(int k)
{
        for(int i=0;i<n;++i)
            if(graph[i][k]!=INF)
            for(int j=0;j<n;++j)
                if(graph[i][j]>graph[i][k]+graph[k][j]) graph[i][j]=graph[i][k]+graph[k][j];   
}
int main()
{
    int casenum=1;
    while(~scanf("%d%d%d",&n,&m,&Q))
    {
        if(n==0 && m==0 && Q==0) break;
        for(int i=0;i<n;++i)
            for(int j=0;j<n;++j)
                if(i!=j) graph[i][j]=INF;
                else graph[i][j]=0;
        for(int i=0;i<m;++i)
        {
            int x,y,cost;scanf("%d%d%d",&x,&y,&cost);
            graph[x][y]=min(cost,graph[x][y]);
        }
        if(casenum>1) printf("\n");
        printf("Case %d:\n",casenum++);
        for(int i=0;i<=n;++i) vis[i]=false;
        while(Q--)
        {
            int op;scanf("%d",&op);
            if(op==0){
                int x;scanf("%d",&x);
                if(vis[x]) printf("ERROR! At point %d\n",x);
                else vis[x]=true,floyd(x);
            }
            else{
                int x,y;scanf("%d%d",&x,&y);
                if(!vis[x] || !vis[y]) printf("ERROR! At path %d to %d\n",x,y);
                else if(graph[x][y]==INF) printf("No such path\n");
                else printf("%d\n",graph[x][y]);
            }
        }
    }
}

4.HDU 1704 Rank

※Floyd求解传递闭包
  这个题想求解的是多组中两个人是否具有传递关系。其实可以转换成一张有向图去做,A能胜过B代表有一条A通向B的单位长度的道路,最后求解一下任意两点之间是否存在最短路(存在一个传递关系)就能确定答案。

#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
using namespace std;
int n,m;
const int INF=0x3f3f3f3f;
int graph[550][550];
void floyd()
{
    for(int k=1;k<=n;++k) 
        for(int i=1;i<=n;++i)
            if(graph[i][k]!=INF)
            for(int j=1;j<=n;++j)
                if(graph[i][j]>graph[i][k]+graph[k][j]) graph[i][j]=graph[i][k]+graph[k][j];
}
int main()
{
    close;int T;cin>>T;
    while(T--)
    {
        cin>>n>>m;
        for(int i=1;i<=n;++i)
            for(int j=1;j<=n;++j)
                graph[i][j]=INF;
        for(int i=0;i<m;++i)
        {
            int x,y;cin>>x>>y;
            graph[x][y]=1;
        }
        floyd();
        int ans=0;
        for(int i=1;i<=n;++i)
            for(int j=i+1;j<=n;++j)
                if(graph[i][j]==INF&&graph[j][i]==INF) ans++;
        cout<<ans<<endl; 
    }
}

5.POJ 1860 Currency Exchange + POJ 3259 Wormholes

  Bellman算法和SPFA算法都可以实现判定正环和负环的目的(通过判断的轮数来确定);Floyd算法可以判断负环,主要是通过出现了两个结点的最短路径是负数来实现。

6.POJ1062 昂贵的聘礼

  这道题首先给出了一个能用几号物品去替换原物品的关系,我们可以根据次关系建边;同时,我们可以建立一个超级源点,连接每一条边的边权即代表购买物品的原始价格。因为是有等级限制的,因此我们可以直接枚举区间即可。

#include<iostream>
#include<cstring>
#include<vector>
#include<queue>
#define close ios::sync_with_stdio(false)
using namespace std;
const int maxn=1e3+100;
const int INF=0x3f3f3f3f;
struct Edge{
    int to,w;
    Edge(int a,int b){to=a;w=b;}
};
vector<Edge> e[maxn];
struct s_node{
    int id,n_dis;
    s_node(int b,int c){id=b;n_dis=c;}
    bool operator <(const s_node &a)const{
        return n_dis>a.n_dis;
    }
};
int n,m,pre[maxn],level[maxn],maxnum=INF;
void Dijkstra(int left,int right)
{
    int dis[maxn];bool done[maxn];
    for(int i=0;i<=n;++i) {dis[i]=INF;done[i]=false;}
    dis[0]=0;
    priority_queue<s_node> Q;
    while(!Q.empty()) Q.pop();
    Q.push(s_node(0,dis[0]));
    while(!Q.empty())
    {
        s_node u=Q.top();Q.pop();
        if(done[u.id]) continue;
        done[u.id]=true;
        for(int i=0;i<e[u.id].size();++i)
        {
            Edge y=e[u.id][i];
            if(level[y.to]<left || level[y.to]>right || done[y.to]) continue;
            if(dis[y.to]>y.w+u.n_dis){
                dis[y.to]=y.w+u.n_dis;
                Q.push(s_node(y.to,dis[y.to]));
                pre[y.to]=u.id;
            }
        }
    }
    if(dis[1]<maxnum) maxnum=dis[1];
}
int main()
{
    close;cin>>m>>n;
    for(int i=0;i<=n;++i) e[i].clear();
    for(int i=1;i<=n;++i)
    {
        int w,x;cin>>w>>level[i]>>x;
        e[0].push_back(Edge(i,w));
        for(int j=1;j<=x;++j){
            int id,val;cin>>id>>val;
            e[id].push_back(Edge(i,val));
        }
    }
    for(int i=level[1]-m;i<=level[1];++i)
        Dijkstra(i,i+m);
    cout<<maxnum;
}

7.POJ 3037
8.HDU 1535 Invitation Cards

※反向建图
  这道题求解的关键在于,对于一个有向图,怎么找到其余的点到起点的最短距离。这里采用反向建图的方式,这样就能把其余的点到起点的最短距离转换成起点到其余的点的最短距离。一次正向建图,一次反向建图,我们就能求解出正确的答案。

#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
using namespace std;
typedef long long ll;
const int maxn=1e6+100;
struct Edge{
	int to,next;ll w;
}e[2][maxn];
int n,m,head[2][maxn],tot1,tot2;
void Add_Edge(int id,int x,int y,ll w)
{
	if(id==1){
		e[id][++tot1].to=y;e[id][tot1].w=w;
		e[id][tot1].next=head[id][x];
		head[id][x]=tot1;
	}
	else{
		e[id][++tot2].to=y;e[id][tot2].w=w;
		e[id][tot2].next=head[id][x];
		head[id][x]=tot2;
	}
}
struct s_node{
	int id;ll n_dis;
	s_node(int b,ll c) {id=b;n_dis=c;}
	bool operator <(const s_node &a)const {return n_dis>a.n_dis;}
};
ll dis[maxn];bool done[maxn];
ll Dijkstra(int op,int s){
	for(int i=1;i<=n;++i) dis[i]=LONG_LONG_MAX,done[i]=false;
	dis[s]=0;
	priority_queue<s_node> Q;
	Q.push(s_node(s,dis[s])); 
	while(!Q.empty())
	{
		s_node u=Q.top();Q.pop();
		if(done[u.id]) continue;
		done[u.id]=true;
		for(int i=head[op][u.id];i;i=e[op][i].next){
			Edge y=e[op][i];
			if(done[y.to]) continue;
			if(dis[y.to]>y.w+u.n_dis){
				dis[y.to]=y.w+u.n_dis;
				Q.push(s_node(y.to,dis[y.to]));
			}
		}
	}
	ll ans=0;
	for(int i=2;i<=n;++i) ans+=dis[i];
	return ans;
}
int main()
{
	close;int T;cin>>T;
	while(T--)
	{
		cin>>n>>m;
		memset(head,0,sizeof(head));
		tot1=0,tot2=0;
		for(int i=1;i<=m;++i)
		{
			int x,y,w;cin>>x>>y>>w;
			Add_Edge(0,x,y,w);
			Add_Edge(1,y,x,w);
		}
		ll ans=Dijkstra(0,1)+Dijkstra(1,1);
		cout<<ans<<endl;
	}
}

9.
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值