洛谷P5663 加工零件(NOIp2019普及组T4)+对Dijkstra和SPFA的理解加深

题目描述

凯凯的工厂正在有条不紊地生产一种神奇的零件,神奇的零件的生产过程自然也很神奇。工厂里有 (n) 位工人,工人们从 (1 sim n) 编号。某些工人之间存在双向的零件传送带。保证每两名工人之间最多只存在一条传送带。

如果 (x) 号工人想生产一个被加工到第 (L (L gt 1)) 阶段的零件,则所有与 (x) 号工人有传送带直接相连的工人,都需要生产一个被加工到第 (L - 1) 阶段的零件(但 (x) 号工人自己无需生产第 (L - 1) 阶段的零件)。

如果 (x) 号工人想生产一个被加工到第 1 阶段的零件,则所有与 (x) 号工人有传送带直接相连的工人,都需要为 (x) 号工人提供一个原材料。

轩轩是 1 号工人。现在给出 (q) 张工单,第 (i) 张工单表示编号为 (a_i) 的工人想生产一个第 (L_i) 阶段的零件。轩轩想知道对于每张工单,他是否需要给别人提供原材料。他知道聪明的你一定可以帮他计算出来!

输入格式

第一行三个正整数 (n)(m)(q),分别表示工人的数目、传送带的数目和工单的数目。

接下来 (m) 行,每行两个正整数 (u)(v),表示编号为 (u)(v) 的工人之间存在一条零件传输带。保证 (u eq v)

接下来 (q) 行,每行两个正整数 (a)(L),表示编号为 (a) 的工人想生产一个第 (L) 阶段的零件。

输出格式

(q) 行,每行一个字符串 (Yes) 或者 (No)。如果按照第 (i) 张工单生产,需要编号为 1 的轩轩提供原材料,则在第 (i) 行输出 (Yes);否则在第 (i) 行输出 (No)。注意输出不含引号。

输入输出样例

输入 #1

3 2 6
1 2
2 3
1 1
2 1
3 1
1 2
2 2
3 2

输出 #1

No
Yes
No
Yes
No
Yes

输入 #2

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

输出 #2

No
Yes
No
Yes
Yes

说明/提示

【输入输出样例 1 说明】

编号为 1 的工人想生产第 1 阶段的零件,需要编号为 2 的工人提供原材料。

编号为 2 的工人想生产第 1 阶段的零件,需要编号为 1 和 3 的工人提供原材料。

编号为 3 的工人想生产第 1 阶段的零件,需要编号为 2 的工人提供原材料。

编号为 1 的工人想生产第 2 阶段的零件,需要编号为 2 的工人生产第 1 阶段的零 件,需要编号为 1 和 3 的工人提供原材料。

编号为 2 的工人想生产第 2 阶段的零件,需要编号为 1 和 3 的工人生产第 1 阶段的零件,他/她们都需要编号为 2 的工人提供原材料。

编号为 3 的工人想生产第 2 阶段的零件,需要编号为 2 的工人生产第 1 阶段的零件,需要编号为 1 和 3 的工人提供原材料。

【输入输出样例 2 说明】

编号为 1 的工人想生产第 1 阶段的零件,需要编号为 2 和 5 的工人提供原材料。

编号为 1 的工人想生产第 2 阶段的零件,需要编号为 2 和 5 的工人生产第 1 阶段的零件,需要编号为 1,3,4 的工人提供原材料。

编号为 1 的工人想生产第 3 阶段的零件,需要编号为 2 和 5 的工人生产第 2 阶段的零件,需要编号为 1,3,4 的工人生产第 1 阶段的零件,需要编号为 2,3,4,5 的工人提供原材料。

编号为 1 的工人想生产第 4 阶段的零件,需要编号为 2 和 5 的工人生产第 3 阶段的零件,需要编号为 1,3,4 的工人生产第 2 阶段的零件,需要编号为 2,3,4,5 的工人生产第 1 阶段的零件,需要全部工人提供原材料。

编号为 1 的工人想生产第 5 阶段的零件,需要编号为 2 和 5 的工人生产第 4 阶段的零件,需要编号为 1,3,4 的工人生产第 3 阶段的零件,需要编号为 2,3,4,5 的工人生产第 2 阶段的零件,需要全部工人生产第 1 阶段的零件,需要全部工人提供原材料。

【数据规模与约定】

共 20 个测试点。

(1 leq u, v, a leq n)

测试点 1~4,(1 leq n, m leq 1000)(q = 3)(L = 1)

测试点 5~8,(1 leq n, m leq 1000)(q = 3)(1 leq L leq 10)

测试点 9~12,(1 leq n, m, L leq 1000)(1 leq q leq 100)

测试点 13~16,(1 leq n, m, L leq 1000)(1 leq q leq 10^5)

测试点 17~20,(1 leq n, m, q leq 10^5)(1 leq L leq 10^9)

思路

(很容易可以想到没有环的情况,只需要判断奇偶性即可。为什么只需要判断奇偶性呢?我们可以想一下,假如说点 A 和点 B 之间有一条边相连,现在需要点 A 生产第7阶段的零件,那么 B 就要生产第6阶段的零件, A 又要生产第5阶段的零件,所以一条边走两遍就只需要判断奇偶性。因为每条边之间没有边权,我们可以假设都是1,这样就可以想到用BFS做。因为只需要判断路径的奇偶性,而且若路径长度过长(比如1号点跟3号点之间的距离是7,但是3号点只需要生产第3阶段的零件,这肯定就不行了)也不行,所以如果这两点之间的最短路过长就不满足条件了,不是最短路只要奇偶性一样,只要走两遍一条边就可以转换回去。所以只需要求出两点之间的奇最短路和偶最短路。由于我们只需要求出1号结点与其它结点的最短路,所以很明显是单源最短路。Dijkstra的原理是贪心,环的情况处理不了(我这个题用不行,可能是我菜QAQ)而SPFA的原理恰巧就是BFS。所以我们就可以用SPFA来求出1号结点与其它结点的奇最短路和偶最短路,再判断一下奇偶性就可以了。别忘记特判两点之间没有奇最短路或偶最短路的情况,这样就可以切掉这道题了。~~我居然用了3天时间才AK了去年的普及组的题目,还看了题解,我是垃圾。~~)

代码

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
const int N=100005,M=200005;
int edge[M],head[N],Next[M],ver[M],d[N];
bool v[N];
int t,n,m,tot;
int ou[N],ji[N];//汉语拼音QAQ
queue<int> q;
void add(int x,int y){
	ver[++tot]=y;
	edge[tot]=1;
	Next[tot]=head[x];
	head[x]=tot;
}//存图
void SPFA(){
    for(int i=0;i<=N-4;i++){
    	ou[i]=10000000;
	}//为了便于判断有没有奇最短路或偶最短路,我存了10000000
    for(int i=0;i<=N-4;i++){
    	ji[i]=10000000;
	}
    memset(v,0,sizeof(v));
	ou[1]=0;
	v[1]=1;
    q.push(1);
    while(!q.empty()){
    	int x=q.front();
    	q.pop();
    	v[x]=0;
    	for(int i=head[x];i;i=Next[i]){
    		int y=ver[i],z=1;
    		if(ou[x]+z<ji[y]){//因为边权都是1,所以偶数肯定是由奇数+1得来,奇数也同理
    			ji[y]=ou[x]+z;
    			if(!v[y]) q.push(y),v[y]=1;
			}
			if(ji[x]+z<ou[y]){
				ou[y]=ji[x]+z;
				if(!v[y]) q.push(y),v[y]=1;
			}
		}
	}
}//正常的SPFA
int main()
{
	scanf("%d%d%d",&t,&n,&m);
	for(int i=1,u,r;i<=n;i++){
		scanf("%d%d",&u,&r);
		add(u,r);
		add(r,u);
	}
	SPFA();
	int a,l;
	while(m--){
		scanf("%d%d",&a,&l);
	    if(l%2==0){
	    	if(ou[a]>l){
	    		printf("No
");
			}//如果最短路距离过长,那么肯定不行
			else{
				if(ou[a]==10000000){
					printf("No
");//没有偶最短路
				}
				else{
					printf("Yes
");
				}
			}
		}
		else{
			if(ji[a]>l){
				printf("No
");
			}
			else{
				if(ji[a]==10000000){
					printf("No
");
				}
				else{
					printf("Yes
");
				}
			}
		}
	}
	return 0;
}

对Dijkstra和SPFA认识的加深

(一开始这个题我是用Dijkstra来做的,但是处理不了有环的情况(也有可能是我写错了),只得了35pts。但是我只改了这一个地方,就是把Dijkstra改成SPFA,它就神奇的A了,这也不得不引起我对这两个最短路算法的加深思考:到底最大的差别是什么?其实它们之间的代码几乎完全一样,只有两个差别:1.Dijkstra用的是小根堆来存储最短路,而SPFA用的是队列。2.Dijkstra不会重复标记点,即这个点我走过之后不会重复走。但是SPFA可以再将出队的点入队,这也是Dijkstra不能处理负边权而SPFA可以的原因。回归证明的原理,Dijkstra的原理是贪心,而SPFA是广搜。由于边权都是正的,所以Dijkstra在与当前点相连的边中取最小的加入小根堆并更新最短路,那么这个最短路肯定不会再被更新。因为如果它被更新,那肯定是这两点之间存在一个断点,使得两点分别到断点的距离之和小于直接相连。但是由于我选的是最短的那条边,所以起点与断点相连的那条边不可能更短,也就是不可能更新最短路。但反之,如果存在负边权,是存在这种情况的,所以出队的元素可能要再入队一遍来更新最小值。对这个题而言,由于是小根堆,它每次都是找最近的那个点然后走,那肯定不能绕一圈回来更新自己,但是SPFA可以,所以不能用Dijkstra而应该用SPFA。)

原文地址:https://www.cnblogs.com/57xmz/p/13366964.html