zoukankan      html  css  js  c++  java
  • 洛谷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。)

  • 相关阅读:
    DNS正向解析实现
    基于bind软件部署DNS服务器
    DNS解析工具使用案例
    DNS服务工作原理
    3
    .deb/.whl/.tgz/.tar/gz软件包安装方式(Ubuntu)
    博客系列目录
    Databricks 第3篇:pyspark.sql 通过JDBC连接数据库
    Databricks 第2篇:pyspark.sql 简介
    数据预处理 第4篇:数据预处理(sklearn 插补缺失值)
  • 原文地址:https://www.cnblogs.com/57xmz/p/13366964.html
Copyright © 2011-2022 走看看