MCS 最短路#4 SPFA

来源:互联网 发布:十分钟学会python 编辑:程序博客网 时间:2024/05/21 10:54

题目链接,可TP


------------------------------------------------------------------------------------------------------

先介绍一下 SPFA:

SPFA算法是1994年西安交通大学  段凡丁 提出的。一种求单元最短路的算法。

算法中需要用到的主要变量

int n;//表示n个点,从1到n标号

int s,t;//s为起点,t为终点

int d[N]; //d[i]表示起点s到点i的最短路

int p[N]; //记录路径(或者说记录前驱)

queue<int> q; //一个队列,用STL实现,当然可有手打队列

int vis[N]; // vis[i] = 1表示点i在队列中,vis[i]= 0 表示 不在队列中

------------------------------------------------------------------------------------------------------

几乎所有的最短路算法其步骤都可以分为两步:

1.初始化

d数组全部复制为MAX (无穷大),p数组全部为s (即起点),或者赋值为-1,表示还没有知道前驱,然后d[s] = 0;表示起点不用求最短路径,或者说最短路 就是0,将起点入队;

2.松弛操作

读取队头顶点u,并将队头顶点u出队(消除标记);将与点u相连的虽有点v进行松弛操作,如果更新估计值(即令d[v]变小),那么就更新,另外,如果点v没有在队列中,那么要将点v入队(标记),如果已经在队列中,不用入队。

以此循环,知道队列为空时,完成了单源最短路的求解。

------------------------------------------------------------------------------------------------------

SPFA可以处理负权边

定理:只要最短路径存在,上述SPFA算法必定能求出最小值。

证明:每次将点放入队尾,都是经过松弛操作达到的。换言之,每次的优化将会有某个点V的最短路径估计值d[V]变小。所以算法的执行会使d越来越小。由于我们假定图中不存在负权回路,所以每个结点都有最短路径值。因此,算法不会无限执行下去,随着d值的变小,直到到达最短路径值时,算法结束,这时的最短路径估计值就是对应结点的最短路径值。(证明完毕)

期望的时间复杂度O(ke),其中k味儿所有顶点进队的平均次数。可以证明k一般小于等于2.


判断有无负环:

如果某个点进入队列的次数超过N次则存在负环(SPFA无法处理带负环的图)


SPFA的两种写法,bfs 和 dfs ,bfs判别负环不稳定,相当于限深度搜索,但是设置得好的的话还是没没问题的,dfs的话判断负环还是很快的

------------------------------------------------------------------------

最短路#4 SPFA 题解

Problem A:
HUD 畅通工程续 1874
题目大意:
输入两个真鳄龟属N,M(0<N<200,0<M<1,000),N是点的数量,M是线的数量,可看做无向图,然后输出M行,每行3个数A,B,X,表示A到B权重为X,再输入两个数S,T,分别代表起点和终点。

这题的四种解法大全---->此处TP

思路:
按照上面介绍的思路一步一步来

#include <iostream>#include <string>#include <vector>#include <set>#include <algorithm>#include <iterator>#include <list>#include <stack>#include <queue>#define MAX 99999999using namespace std;int mp[205][205];int dis[205];int vis[205];int n,m;int st,en;void SPFA(){int i;//初始化路线总和   &&  初始化标记for(i=0;i<n;i++){dis[i]=MAX;vis[i]=0;}//创立队列,清空队列queue<int> q;while(!q.empty())q.pop();//起始点标记dis[st]=0;vis[st]=1;q.push(st);while(!q.empty()){int t;t=q.front();q.pop();vis[t]=0;for(i=0;i<n;i++)//找出比之前更短的路径,松弛if(dis[i]>dis[t]+mp[t][i]){dis[i] = dis[t] + mp[t][i];//若没有走过,标记已经走过,并放入队列if( !vis[i]){vis[i]=1;q.push(i);}}}}int main(){int u,v,cost;int i,j;while(cin>>n>>m)//n为点,m为边{//初始化邻接矩阵for(i=0;i<n;i++)for(j=0;j<n;j++)mp[i][j]=MAX;for(i=0;i<m;i++){cin>>u>>v>>cost;if(cost<mp[u][v])mp[u][v]=mp[v][u]=cost;}//输入起始点,和结束点cin>>st>>en;//SPFASPFA();//输出结果if(dis[en] < MAX)cout<<dis[en]<<endl;elsecout<<-1<<endl;}return 0;}


Problem B

POJ 1201 Intervals

题目大意: 
输入一个整数n,(1<=n<=50.000)n表示有n个区间,接下来n行,每行3个数ai,bi,ci(0<=ai<=bi<=50.000 and 1<= ci<=  bi-ai+1)在ai到bi中间 有ci个数要取出,求总共要去除多少数

思路:

百度了一下。此题需用:差分约束+spfa,思路看---->链接

百度大法好呀,百度大法好!



#include <iostream>#include <cstdio>#include <string>#include <queue>#include <cstring>using namespace std;const int M = 151010;const int INF = -9999999;struct Edge   //邻接表{    int v;    int w;    int next;  //} edge[M];int dis[M];int head[M];  //保存边的头结点编号bool visit[M];int num,n,maxx,minn;int number ;queue<int> p;int SPFA(){    int e;    for(int i = minn; i <= maxx; i++)        dis[i] = INF;    dis[minn] = 0;    p.push(minn);//源点入队    visit[minn] = true; //标记在队中    while( !p.empty() )    {        e = p.front();        p.pop();        visit[e] = false; //标记不在队中        for(int i = head[e]; i != -1; i = edge[i].next)        {            if (dis[edge[i].v] < dis[e] + edge[i].w)   //找最长路,三角不等式            {                dis[edge[i].v] = dis[e] + edge[i].w;                if( !visit[edge[i].v] )                {                    visit[edge[i].v] = true;                    p.push(edge[i].v);                }            }        }    }    return dis[maxx];}int main(){    int a,b,c;    num = 0;    number = 1;    maxx = -9999;    minn = 9999;    memset(dis,0,sizeof(dis));    memset(head,-1,sizeof(head));  //不要写0啊啊啊啊    memset(visit,false,sizeof(visit));    cin >> n;    for(int i = 1; i <= n; i++)    {        cin >> a >> b >> c;        edge[num].next = head[a];        edge[num].v = b + 1;        edge[num].w = c;        head[a] = num;        num ++;        if(maxx < b+1)            maxx = b+1;        if(minn > a)            minn = a;    }    for(int i = minn; i < maxx; i++)    {        edge[num].next = head[i];        edge[num].v = i+1;        edge[num].w = 0;        head[i] = num;        num++;        edge[num].next = head[i+1];        edge[num].v = i;        edge[num].w = -1;        head[i+1] = num;        num++;    }    int ans = SPFA();    cout << ans <<endl;    return 0;}


Problem C

POJ 3169 Layout
题目大意:
有n头牛排成一条线,输入一个数字N(2<=N<=1,000),ML,MD(1<=ML<=10,000,1<=MD<=10,000)
接着以ML的形式,输入ML行
输A,B,D三个数(1<=A<B<=N,1<=D<=1,000,000)A和B之间最大距离为D
接着以MD的形式,输入MD行
输A,B,D三个数(1<=A<B<=N,1<=D<=1,000,000)A和B之间最大距离为D
若不存在最大距离(存在负环)输出-1,若距离可以任意 输出-2,否则输出最大距离
思路:
首先对于任一两头相邻的牛,它们之间的距离大于或等于0;其次,由ML个条件得到S[b]-S[a] <= d,
最后由MD个条件得到S[a]-S[b]<= -d.
百度大法好啊,百度大法好!

#include <cstdio>#include <cstring>#include <algorithm>#include <queue>#include <iostream>#define INF 0x3f3f3f3f#define maxn  1100using namespace std;struct node {int u, v, w, next;};node edge[1100000];int dist[maxn], head[maxn], cnt;int used[maxn], n, ML, MD;bool vis[maxn];//初始化void init(){cnt = 0;memset(head, -1, sizeof(head));memset(used, 0, sizeof(used));memset(vis, 0 ,sizeof(vis));}void add(int u, int v, int w){edge[cnt].u=u; edge[cnt].v=v;edge[cnt].w=w;edge[cnt].next=head[u];head[u] = cnt++;}//输入ML和MD的两种情况void getmap(){int a, b, d;for(int i = 1; i <= n; ++i)add(i + 1, i, 0);//增加最大距离while(ML--){cin>>a>>b>>d;add(a, b, d);}//减掉最短距离while(MD--){cin>>a>>b>>d;add(b, a, -d);}}void SPFA(){for(int i = 1; i <= n; ++i)dist[i] = INF;dist[1] = 0;queue<int>q;vis[1] = 1;used[1] = 1;q.push(1);while(!q.empty()){int u = q.front();q.pop();if(used[u] > n){printf("-1\n");return ;}vis[u]  = 0;for(int i = head[u]; i != -1; i = edge[i].next){int v = edge[i].v;int w = edge[i].w;if(dist[v] > dist[u] + w){dist[v] = dist[u] + w;if(!vis[v]){vis[v] = 1;used[v]++;q.push(v);}}}}if(dist[n] == INF)cout<<"-2"<<endl;else cout<<dist[n]<<endl;return ;}int main (){while(cin>>n>>ML>>MD){init();getmap();SPFA();}return 0;}


Problem D

POJ 1511 Invitation Cards
题目大意:

输入N,N组测试,每一组测试输入P,Q两个数,(1<=P,Q<=1,000,000),最大数字为P,接下来有Q行,每一行a,b,w,在a,b之间的路线权为w,这是个有向图。

思路:

按照上面几道题的思路

百度大法好呀,百度大法好


#include <stdio.h>#include <stdlib.h>#include <string.h>#define nMax 1000010#define eMax 10000010#define inf 1000000010struct EDGE{int v,w,next;}edge[nMax], reEdge[nMax];int nEdge[nMax], nReEdge[nMax];int n, m, queue[eMax];__int64 dis[nMax], sum;bool vis[nMax];void edgeInit(){for (int i = 1; i <= n; ++ i){nEdge[i] = 0;nReEdge[i] = 0;}}void spfaInit(){for (int i = 1; i <= n; ++ i){dis[i] = inf;vis[i] = false;}}void spfa(int * nEdge, EDGE * edge){spfaInit();int head = 0, tail = 1;dis[1] = 0;queue[0] = 1;while (head < tail){int u = queue[head];vis[u] = true;int p = nEdge[u];while (p != 0){int v = edge[p].v, w = edge[p].w;if (dis[v] > dis[u] + w){dis[v] = dis[u] + w;if (!vis[v]){vis[v] = true;queue[tail] = v;tail ++;}}p = edge[p].next;}vis[u] = false;head ++;}for (int i = 1; i <= n; ++ i){sum += dis[i];}}int main(){int t;scanf("%d", &t);while (t --){scanf("%d%d", &n, &m);edgeInit();for (int i = 1; i <= m; ++ i){int u, v, w;scanf("%d%d%d", &u, &v, &w);edge[i].v = v;edge[i].w = w;edge[i].next = nEdge[u];nEdge[u] = i;reEdge[i].v = u;reEdge[i].w = w;reEdge[i].next = nReEdge[v];nReEdge[v] = i;}sum = 0;spfa(nEdge, edge);spfa(nReEdge, reEdge);printf("%I64d\n", sum);}return 0;}


Problem EHEU 

最短路径问题 3790

题目大意:

输入两个整数n,m(1<n<=1,000,0<m<100,000),n是点的数量,m是边的数量,n,m为0时结束。可看作无向图,然后输入m行,每行4个数a,b,d,p,表示a和b之间有一条边其长度为d,花费为q,再输入两个数,s和t,分别为起点和终点。输出最短距离(当最短距离有多条路线,输出花费最少的)

思路:

总的思路差不多。然后主要是再多一个花费。

这题不百度不行啊!............

#include <iostream>#include <string>#include <vector>#include <set>#include <algorithm>#include <iterator>#include <list>#include <stack>#include <queue>using namespace std;const int MAX = 1100;const int INF = 0x3fffffff;struct Node{    int len;    int money;};Node map[MAX][MAX];Node dis[MAX];bool used[MAX];Node SPFA(int start,int end,int n){    int i;//初始化    for(i=1;i<=n;i++)    {        dis[i].len = INF;        dis[i].money = INF;    }//起始位置    dis[start].len = 0;    dis[start].money = 0;//建立队列    queue <int> q;    q.push(start);    used[start] = 1;    while(!q.empty())    {        int mid;        mid = q.front();        q.pop();        used[mid] = 0;        for(i=1;i<=n;i++)        {//松弛操作            if(map[mid][i].len + dis[mid].len < dis[i].len)            {                dis[i].len = map[mid][i].len + dis[mid].len;                dis[i].money = map[mid][i].money + dis[mid].money;                if(!used[i])                {                    q.push(i);                    used[i] = 1;                }            }//当最短距离有多条的时候,输出花费最少的            if(map[mid][i].len + dis[mid].len == dis[i].len && dis[i].money > map[mid][i].money + dis[mid].money)            {                dis[i].money = map[mid][i].money + dis[mid].money;            }        }    }    return dis[end];}int main(){    int n,m;    while(cin>>n>>m,n+m)    {        int i,j;//初始化邻接矩阵        for(i=1;i<MAX;i++)            for(j=1;j<MAX;j++)            {                map[i][j].len = INF;                map[i][j].money = INF;            }        memset(used,0,sizeof(used));//初始化边的长度和花费        for(i=0;i<m;i++)        {            int a,b,c,d;            cin>>a>>b>>c>>d;            if(c < map[a][b].len)            {                map[a][b].len = c;                map[b][a].len = c;                map[a][b].money = d;                map[b][a].money = d;            }        }//输入起始点,和结束点        int s,e;        cin>>s>>e;//SPFA        Node x = SPFA(s,e,n);//输出结果        cout<<x.len<<" "<<x.money<<endl;    }    return 0;}





0 0
原创粉丝点击