HDU 6386 Age of Moyu(透彻理解BFS求最短距离+DFS)

Time Limit: 5000/2500 MS (Java/Others)    Memory Limit: 262144/262144 K (Java/Others)

Problem Description

Mr.Quin love fishes so much and Mr.Quin’s city has a nautical system,consisiting of N ports and M shipping lines. The ports are numbered 1 to N. Each line is occupied by a Weitian. Each Weitian has an identification number.

The i-th (1≤iM) line connects port Ai and Bi (AiBi) bidirectionally, and occupied by Ci Weitian (At most one line between two ports).

When Mr.Quin only uses lines that are occupied by the same Weitian, the cost is 1 XiangXiangJi. Whenever Mr.Quin changes to a line that is occupied by a different Weitian from the current line, Mr.Quin is charged an additional cost of 1 XiangXiangJi. In a case where Mr.Quin changed from some Weitian A's line to another Weitian's line changes to Weitian A's line again, the additional cost is incurred again.

Mr.Quin is now at port 1 and wants to travel to port N where live many fishes. Find the minimum required XiangXiangJi (If Mr.Quin can’t travel to port N, print −1 instead)

Input

There might be multiple test cases, no more than 20. You need to read till the end of input.

For each test case,In the first line, two integers N (2≤N≤100000) and M (0≤M≤200000), representing the number of ports and shipping lines in the city.

In the following m lines, each contain three integers, the first and second representing two ends Ai and Bi of a shipping line (1≤Ai,BiN) and the third representing the identification number Ci (1≤Ci≤1000000) of Weitian who occupies this shipping line

Output

For each test case output the minimum required cost. If Mr.Quin can’t travel to port N, output −1 instead.

Sample Input

3 3 
1 2 1
1 3 2
2 3 1
2 0
3 2
1 2 1
2 3 2

Sample Output

1

-1

2

Source

2018 Multi-University Training Contest 7

题解:

一看就是将花费变成路径的权值的最短路类问题,刚开始想跑SPFA或Dijkstra,但边的关系处理起来比较麻烦而且耗时更多,所以就想是否可以用经典的BFS求最短路径。毕竟BFS的话每次到一个点时我们是知道当前路径与这个点的直接连边的占领者的,而SPFA过程中是不知道的。

ERROR:具体过程其实与普通的BFS求最短路也没啥区别,就是需要一个数组(dis)来记录起点(1)到所有点的最短距离,然后每个点可以多次加入queue(用优先队列)。(这种方法已发现是有BUG的)

感谢@jianbagengmu给出样例

5 6
1 2 1
2 3 1
3 4 1
4 5 1
1 3 2
1 4 3

这种方法当遇到多条线路到达同一个点的花费相同但线路占领者不同时就会出问题。不过由于出题人可能没有考虑这种情况,导致这种错误方法也能AC。(事实上多校时我就是用这种方法过的。。。_(:з」∠)_)

正确方法:

bfs查询路径,每条边只走一次。每一个点入队列后,再用dfs把所有与当前边相同占领者的边的点加入。本质上是用dfs来压缩路径,然后bfs。因为按照普通的bfs找最短路径经验我们容易发现两个典型的性质

1:每次以当前点为中心加进来的相邻点的距离都为当前点距离+1。

2:必定为最短距离。

也就是“层层递进”,如图分成3层。因为从第1层到第2层任一点必定存在距离为1的路,第2层到第3层也是,所以我们甚至可以将3个层映射到直线上三个点如:1->2->3。那么从1到3的最短距离显而易见。

到此通过分析了BFS求最短距离之后我们发现只需要在这道题中分出“层”就能按BFS老套路求解。很容易想到被相同占领者的边连起来的联通块应该当作一个“格子”(通过dfs),而距离起点距离相同的“格子”自然就是一个“层”。

正确代码:

#include <bits/stdc++.h>

using namespace std;
 
const int MAXN = 100005;
const int INF = 0x3f3f3f3f;
 
int N,M;
 
struct Edge{
    int to,id,next;//id是边的占领者 
    bool vis;
}E[MAXN*4];
 
int head[MAXN],tot;
queue<int> Q;
 
inline void Add(int from,int to,int id){
    E[++tot].next = head[from];
    E[tot].to = to;
    E[tot].id = id;
    E[tot].vis = false;
    head[from] = tot;
    E[++tot].next = head[to];
    E[tot].id = id;
    E[tot].to = from;
    E[tot].vis = false;
    head[to] = tot;
}
 
inline void init(){
    tot = 0;
    memset(head,0,sizeof head);
}
 
int dis[MAXN];//记录起点到所有点的最短距离
bool vis[MAXN];

void DFS(int x,int id,int num){
	if(!vis[x]){
		dis[x] = num;
		vis[x] = true;
		if(x == N)return;
		Q.push(x);
	}
	for(int i=head[x] ; i ; i=E[i].next){
		if(E[i].vis)continue;
		if(E[i].id == id){
			E[i].vis = true;
			DFS(E[i].to,id,num);
		}
	}
}
 
void BFS(int x){
	while(!Q.empty())Q.pop();
	memset(vis,false,sizeof vis);
	dis[x] = 0;
	dis[N] = -1;
	vis[x] = true;
	Q.push(x);
	while(!Q.empty()){
	    int t = Q.front();
	    Q.pop();
	    for(int i=head[t] ; i ; i=E[i].next){
	       if(E[i].vis)continue;
	       E[i].vis = true;
	       DFS(E[i].to,E[i].id,dis[t]+1);
	       if(dis[N] > 0)break;
	    }
	    if(dis[N] > 0)break;
	}
}
 
int main(){
    
    while(scanf("%d %d",&N,&M) == 2){
        init();
        if(M == 0){
            printf("-1
");
            continue;
        }
        int a,b,c;
        for(int i=1 ; i<=M ; ++i){
            scanf("%d %d %d",&a,&b,&c);
            Add(a,b,c);
        }
        BFS(1);
        printf("%d
",dis[N]);
    }
    
    return 0;
}

错误代码(但也能AC_(:з」∠)_):

#include <cstdio>
#include <queue>
#include <algorithm>
#include <cstring>

using namespace std;

const int MAXN = 200005;
const int INF = 0x3f3f3f3f;

int N,M;

struct Edge{
    int to,id,next;//id是边的所属 
}E[MAXN*2];

int head[100005],tot;

inline void Add(int from,int to,int id){
    E[++tot].next = head[from];
    E[tot].to = to;
    E[tot].id = id;
    head[from] = tot;
    E[++tot].next = head[to];
    E[tot].id = id;
    E[tot].to = from;
    head[to] = tot;
}

inline void init(){
    tot = 0;
    memset(head,0,sizeof head);
}

struct D{
    int num,w,kind;//点的编号,权值,上一步到此点所走路径的所属。 
    D(){}
    D(int _num,int _w,int _kind):num(_num),w(_w),kind(_kind){}
};

struct cmp{
    bool operator ()(D a,D b){
        return a.w > b.w;
    }
};

int dis[100005];//记录起点到所有点的最短距离

int BFS(int x){
    priority_queue<D,vector<D>,cmp> Q;
    memset(dis,INF,sizeof dis);
    dis[x] = 0;
    Q.push(D(x,0,-1));
    struct D t;
    while(!Q.empty()){
        t = Q.top();
        Q.pop();
        if(t.w > dis[t.num])continue;//如果权值大于最短路径代表是个“废的”直接跳过。 
        if(t.num == N)return t.w;
        for(int i=head[t.num] ; i ; i=E[i].next){
            int l;
            if(E[i].id == t.kind)l = dis[t.num];
            else l = dis[t.num] + 1;
            if(l < dis[E[i].to]){
                dis[E[i].to] = l;
                Q.push(D(E[i].to,l,E[i].id));
            }
        }
    }
    return -1;
}

int main(){
    
    while(scanf("%d %d",&N,&M) == 2){
        init();
        if(M == 0){
            printf("-1
");
            continue;
        }
        int a,b,c;
        for(int i=1 ; i<=M ; ++i){
            scanf("%d %d %d",&a,&b,&c);
            Add(a,b,c);
        }
        printf("%d
",BFS(1));
    }
    
    return 0;
}
原文地址:https://www.cnblogs.com/vocaloid01/p/9514003.html