题解 P5663 【加工零件【民间数据】】

· · 题解

题目链接

0.前记

考场上因为时间紧迫加上近视度数过深,代码敲成"YES"和"NO",喜提0分的好成绩

1.思路

i.

从最简单的图开始

我们不难发现,对于1想做2n阶段时,就必须做2n,2n-2,...0阶段的零件,而此时2对应做的时2n-1,2n-3...1阶段

同样的,若1想做2n-1阶段时,就必须做2n-3...1阶段,2应该做2n-2,2n-4...0阶段

总而言之,对于第i个点而言,当它想做阶段L的零件

·如果L为偶数,且存在i走偶数步可以到1的方法,则Yes

·如果L为奇数,且存在i走奇数步可以到1的方法,则Yes

·若不满足以上两种情况,必No

ii.

同样找一个简单图

我们思考一个问题,如果1要做一个阶段1的零件,它该是Yes还是No呢

如果按照之前找到的奇偶来找,存在一条1-2-3-1的路径,它的长度为奇数3,那么应该是Yes

但是我们要想,1阶段刚走出去到2和3,2和3做0阶段之后,就没得再走下去了

毕竟没有-1阶段这种东西吗

那么我们得到第二个结论:

若L还不到i到1的最短路,那么也是No

我们把上面两种情况的结论合并一下:

记从i点走偶数步到1点的最短路为dis[i][0],走奇数步为dis[i][1]

·如果L为偶数,且dis[i][0]<=L则输出Yes

·如果L为奇数,且dis[i][1]<=L则输出Yes

·以上条件均不满足,输出No

那么我们就可以想到应该分奇偶两种跑最短路

2.实现细节

i.特殊情况

如果没有任何节点与1相连,那么无论如何都输出No

ii.算法选择

题解里有好多人用dij,spfa的,事实上在这题里面完全没有必要

我们发现由于每条边的长度都是1,也就是说后遍历到的绝对比前面遍历到的点的距离要远

直接搞一个队列bfs即可

每次找到一个点,判断一下之前同奇偶性的这个点走过没有,有过就舍弃,否则就入队尾

每个点保证最多奇偶各进一次,时间复杂度最满也是O(2n)

iii.常数优化(可无视,纯属卡常和压行癖好)

初始的时候直接令dis[i][0]=dis[i][1]=1e9+7,既可以当判断是否遍历过的数组,还可以作为记录距离的数组,如果无路直接判断1e9+7>L输出No

当然dis[1][0]=0

iv.异或的使用(又一个卡常和压行)

在代码中,我们用0代表偶数步的情况,1代表奇数步的情况

对于偶数步,我们再走一步就是奇数步了,即0转移到的是1

同理,1转移到的是0

因此我们可以用^1来转移,即0^1=1 1^1=0

3.代码

#include<cstdio>
#include<cctype>
const int maxn=1e5+5;
inline char nc()
{
    static char buf[1000000],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++;
}
inline int R(){//快读,没错又是卡常 
    int r=0;char c=nc();
    while(!isdigit(c)) c=nc();
    while(isdigit(c)) r=(r<<1)+(r<<3)+(c^48),c=nc();
    return r;
}
int n,m,q,head[maxn],tot,dis[maxn][2],dl[maxn<<1][2];
//dl[i][0]:节点编号,dl[i][1]:所对应的情况是偶数还是奇数,对应0和1
//dis[i][0]:i点走偶数步到1的最短路,dis[i][1]同 
bool d;//判断节点1有无连边 
struct node{
    int to,next;
}edge[maxn<<1];
inline void add(int from,int to){
    edge[++tot].next=head[from];
    edge[tot].to=to;
    head[from]=tot;
    return ;
}
inline void work(){//bfs
    dl[1][0]=1;dl[1][1]=0;dis[1][0]=0;//入队加初始化 
    int l=0,r=1;
    while(l<r){
        l++;int k=dl[l][0];//k为当前遍历到的节点 
        for(register int i=head[k];i;i=edge[i].next){
            int u=edge[i].to;//u为下一个节点 
            if(dis[u][dl[l][1]^1]!=1e9+7) continue;//如果下一个节点的dl[l][1]^1走过,就不要走了 
            dis[u][dl[l][1]^1]=dis[k][dl[l][1]]+1;//覆盖dis 
            dl[++r][0]=u;dl[r][1]=dl[l][1]^1;//下一个节点入队 
        }
    }
}
int main(){
    n=R();m=R();q=R();
    for(register int i=1;i<=m;i++){
        int a=R(),b=R();
        add(a,b);add(b,a);
        if(a==1 || b==1) d=1;//只要1出现,就有连边 
    }
    for(register int i=1;i<=n;i++) dis[i][0]=dis[i][1]=1e9+7;//初始化 
    if(d) work();//1没连边就没有必要跑work了(也是为了卡常) 
    for(register int i=1;i<=q;i++){
        int a=R(),l=R();
        if(d && l>=dis[a][l&1]) puts("Yes");//满足条件就输出Yes
        else puts("No");//否则输出No 
    }
    return 0;
}

4.运行效果

5.后记

本题的思维难度还是有那么一点的,但是一旦想到,问题就可以迎刃而解

Finally,谢谢大家

更好的阅读体验