hdoj 1874 通畅工程续 (最短路)

来源:互联网 发布:linux 复制目录及文件 编辑:程序博客网 时间:2024/06/09 17:32

Problem Description
某省自从实行了很多年的畅通工程计划后,终于修建了很多路。不过路多了也不好,每次要从一个城镇到另一个城镇时,都有许多种道路方案可以选择,而某些方案要比另一些方案行走的距离要短很多。这让行人很困扰。

现在,已知起点和终点,请你计算出要从起点到终点,最短需要行走多少距离。
 

Input
本题目包含多组数据,请处理到文件结束。
每组数据第一行包含两个正整数N和M(0<N<200,0<M<1000),分别代表现有城镇的数目和已修建的道路的数目。城镇分别以0~N-1编号。
接下来是M行道路信息。每一行有三个整数A,B,X(0<=A,B<N,A!=B,0<X<10000),表示城镇A和城镇B之间有一条长度为X的双向道路。
再接下一行有两个整数S,T(0<=S,T<N),分别代表起点和终点。
 

Output
对于每组数据,请在一行里输出最短需要行走的距离。如果不存在从S到T的路线,就输出-1.
 

Sample Input
3 30 1 10 2 31 2 10 23 10 1 11 2
 

Sample Output
2-1

/*dijkstra算法:其实它长得和prime算法非常像,不同的是,prime算法里每次更新的lowcost集合是红点集与蓝点集之间的最小权值路径,然后将这些最小路径里再找最小的一个加入ans,最后的这个ans就是生成树的权值。而dijkstra里,dis更新的是从开始点到其他每个点的最短长度。开始时dis先初始化为无穷大,然后从开始点出发,找出一个权值最小的路,将其对应的点标记为vis=1(第一次循环时这个点必定是开始点),换句话说就是把这个点加入了红点集。每次找的新点:看看是从原先的路过去近,还是经过红点集中的点再过去近。如果后者,就更新dis,找出dis中最小的,对应的点就是新点。之前学数据机构模拟手算的时候我还有个疑问,就是假如红点集中的点是1,2,3,那么我找到4的距离的时候,是比较从1到4还是从1到2到4还是从1到2到3到4呢?这样的话点越多就越不好判断了呀。其实算法中,dis是一直更新的,保存的不一直是1-4距离,而是每加入一个点,就看看是不是更短。例如:加入2之后,就比较1-4和1-2-4,发现后者更小,那么dis就变成了1-2-4.又加入了3,就比较1-2-4(就是此时的dis)和1-2-3-4.也就是看dis[j]与dis[k]+map[k][j](这里的j为当前考察的点,即4,k是刚刚加入红点集的新点),所以dis保存的一直是已存所有路径中最短的。这里的道理和lowcost一样。*/#include <stdio.h>#include <string.h>#define INF 0xfffffffint map[210][210], vis[210], s, t, n, m, dis[210];int dijkstra(){    int i, j, k, min = INF;    dis[s] = 0;    for(i = 0 ; i < n ; i++)    {        min = INF;        for(j = 0 ; j < n ; j++)        {            if(!vis[j] && min > dis[j])            {                min = dis[j];                k = j;            }        }        vis[k] = 1;        for(j = 0 ; j < n ; j++)        {            if(!vis[j] && dis[j] > (dis[k] + map[k][j]))//比较dis                dis[j] = dis[k] + map[k][j];        }    }    if(!vis[t])        return -1;    return dis[t];}int main(){    int i, j, a, b, x;    while(~scanf("%d %d", &n, &m))    {        for(i = 0 ; i < n ; i++)    {        dis[i] = INF;//之前忘了把最短距离先初始化为无穷大        for(j = 0 ; j < n ; j++)                map[i][j] = INF;    }memset(vis, 0, sizeof(vis));        for(i = 0 ; i < m ; i++)        {            scanf("%d %d %d", &a, &b, &x);            if(x < map[a][b])                map[a][b] = map[b][a] = x;        }        scanf("%d %d", &s, &t);        printf("%d\n", dijkstra());    }    return 0;}
//bellman-ford:#include <stdio.h>#include <string.h>#define INF 0xffffffftypedef struct{    int u, v, cost;}node;node edge[10050];int s, t, n, m, dis[210];int ford(){    int i, j;    dis[s] = 0;    for(i = 0 ; i < n-1 ; i++)        for(j = 0 ; j < 2*m ; j++)    {        if(dis[edge[j].v] > dis[edge[j].u] + edge[j].cost)            dis[edge[j].v] = dis[edge[j].u] + edge[j].cost;    }    if(dis[t] == INF)        return -1;    return dis[t];}int main(){    int i;    while(~scanf("%d %d", &n, &m))    {        for(i = 0 ; i < n ; i++)        {            dis[i] = INF;//之前忘了把最短距离先初始化为无穷大        }        for(i = 0 ; i < m ; i++)        {            scanf("%d %d %d", &edge[i].u, &edge[i].v, &edge[i].cost);            edge[m+i].v = edge[i].u;            edge[m+i].u = edge[i].v;            edge[m+i].cost = edge[i].cost;        }        scanf("%d %d", &s, &t);        printf("%d\n", ford());    }    return 0;}


问题描述

给定一个n个顶点,m条边的有向图(其中某些边权可能为负,但保证没有负环)。请你计算从1号点到其他点的最短路(顶点从1到n编号)。

输入格式

第一行两个整数n, m。

接下来的m行,每行有三个整数u, v, l,表示u到v有一条长度为l的边。

输出格式
共n-1行,第i行表示1号点到i+1号点的最短路。
样例输入
3 3
1 2 -1
2 3 -1
3 1 2
样例输出
-1
-2
数据规模与约定

对于10%的数据,n = 2,m = 2。

对于30%的数据,n <= 5,m <= 10。

对于100%的数据,1 <= n <= 20000,1 <= m <= 200000,-10000 <= l <= 10000,保证从任意顶点都能到达其他所有顶点。

/*spfa算法:形式和广搜很像。虽然效率不如dijkstra高,但它可以处理有负权的情况。(dijkstra在边有负权的情况下求出来的最短路是错误的)哎呀,这种方法在我第一次见到最短路并且当时还不知道dij算法的时候就想到过了,只是没有付诸实施……不过我当时只想到了邻接矩阵的算法,就是打一个邻接矩阵,像走迷宫一样广搜。搜到一个点就跳到其对应的行上继续搜。这里由于n太大,所以邻接矩阵不可行,得用邻接表存储。这里的邻接表存储是模拟了链表,用head数组表示从同一个点出发的不同的边连成的线性表。如head[2]:2-3->2-4->-1。其中head数组的下标是谁就是这个表的起边都是谁,如上边那个例子就是这个线性表的起边都是2,但终边不相同。head的内容是保存了当前线性表的头结点,在这里是2-3边的序号。计算过程就是从起点开始,选择它所在的那一条线性表,逐个松弛。其中能松弛的,就说明这条路被更新了,那么其对应的终点就要入队,意思是到这条路终点的那一段路被更新得更小了,所以这个终点连着的其它点可能也要被更新。这里需要注意的一点就是标记。Bellman-Ford算法中没有这一点,不管三七二十一都进行判断松弛操作。而这里是对它进行了改进,防止了多余的判断松弛。(松弛操作是都有的,能松就松,但松了以后不一定再入队了)例如:2-4->2-3->-1, 3-4->-1。松弛了2-4,把4入了队。再下一个3-4中,也进行了松弛,但这里的4就不需要入队了。防止了多余的操作。出队的点可以再入队。所以每出一个点,就把vis改回0.*/#include <stdio.h>#include <queue>#include <string.h>#define l 20005#define inf 0xfffffffusing namespace std;struct e{    int x, y, w, next;}edge[l*10];int n, m, head[l], vis[l], dis[l];queue <int> q;void spfa(int choose){    int u, v, i;    vis[choose] = 1;    dis[choose] = 0;    q.push(choose);    while(!q.empty())    {        u = q.front();        q.pop();        vis[u] = 0;//出队的点标记回未访问。只有出过队的点才有机会再入队。所以标记数组vis必须有。        for(i = head[u] ; i != -1 ; i = edge[i].next)        {            v = edge[i].y;            if(dis[v] > dis[u] + edge[i].w)            {                dis[v] = dis[u] + edge[i].w;                if(!vis[v])                {                    q.push(v);                    vis[v] = 1;                }            }        }    }}int main(){    int i, x, y, w;    scanf("%d %d", &n, &m);    for(i = 1 ; i <= n ; i++)    {        dis[i] = inf;        head[i] = -1;//head[i]表示从点i出去的最后那一条边的序号。        vis[i] = 0;    }    for(i = 0 ; i < m ; i++)    {        scanf("%d %d %d", &x, &y, &w);        edge[i].x = x;        edge[i].y = y;        edge[i].w = w;        edge[i].next = head[x];//模拟链表。head数组的下标表示是属于哪个点的那条串,内容是上一次的属于这条串的边的序号。        head[x] = i;//移动头结点为新的序号。    }    spfa(1);    for(i = 2 ; i <= n ; i++)        printf("%d\n", dis[i]);    return 0;}



0 0
原创粉丝点击