P5663 加工零件 题解

P5663 加工零件 题解

原文链接:http://suo.im/5Xi8f9

确定做法

首先,看到这道题,我直接想到的是递归,于是复杂度就上天了,考虑最短路

如何用最短路

首先,看一张图

360截图16251114373524.png

我们该如何解决问题?

问题: \(3\)\(5\)阶段的零件 \(1\)要不要做呢?

其实,实质就是看 \(3\)\(1\)有没有长度为 \(5\)的路径。

问题: \(3\)\(7\)阶段的零件 \(1\)要不要做呢?

其实,实质就是看 \(3\)\(1\)有没有长度为 \(7\)的路径。

问题: \(3\)\(6\)阶段的零件 \(1\)要不要做呢?

其实,实质就是看 \(3\)\(1\)有没有长度为 \(6\)的路径。

仔细思考这 \(3\)个问题,我们会发现,如果 \(3\)\(1\)有长度为 \(5\)的路径,那么 \(3\)\(1\)一定有长度为 \(7\)的路径,但并不一定有长度为 \(6\)的路径。

所以,我们要对每个点求一遍奇数路径,和偶数路径。

实现最短路

最短路的算法有很多,这道题最好用 \(dijkstra\),或 \(bfs\)

这道题的时限并不紧,并且 \(dijkstra\)细节太多,我就来演示 \(bfs\)实现的最短路

void bfw(){//我有一个好朋友叫bfw,所以我写bfs时,喜欢把函数名起为bfw
    memset(ji,0x3f,sizeof(ji));//奇数最短路径
    memset(ou,0x3f,sizeof(ou));//偶数最短路径
    queue<pair<int,int> >q;
    q.push(make_pair(1,0));
    ou[1]=0;
    while(q.size()){
        int x=q.front().first,y=q.front().second;
        for(int i=0;i<v[x].size();i++){
            if(y%2==1){//奇数+1=偶数
                if(y+1<ou[v[x][i]]){
                    ou[v[x][i]]=y+1;//更新答案
                    q.push(make_pair(v[x][i],y+1));
                }
            }else{//偶数+1=奇数
                if(y+1<ji[v[x][i]]){
                    ji[v[x][i]]=y+1;//更新答案
                    q.push(make_pair(v[x][i],y+1));
                }
            }
        }
        q.pop();
    }
}

\(v\)数组是一个动态数组,也就是 \(vector\),曹老师教我们多用 \(STL\)写程序

如果你写这样的 \(bfs\)民间数据会 \(WA\) \(1\)个点 ,这个点是这样的

360截图172905077510285.png

\(1\)号点是一个孤点,没有偶数路径,所以,我们的 \(bfs\)要这么写

void bfw(){//我有一个好朋友叫bfw,所以我写bfs时,喜欢把函数名起为bfw
    memset(ji,0x3f,sizeof(ji));//奇数最短路径
    memset(ou,0x3f,sizeof(ou));//偶数最短路径
    queue<pair<int,int> >q;
    for(int i=0;i<v[1].size();i++){
        ji[v[1][i]]=1;
        q.push(make_pair(v[1][i],1));
    }
    while(q.size()){
        int x=q.front().first,y=q.front().second;
        for(int i=0;i<v[x].size();i++){
            if(y%2==1){//奇数+1=偶数
                if(y+1<ou[v[x][i]]){
                    ou[v[x][i]]=y+1;//更新答案
                    q.push(make_pair(v[x][i],y+1));
                }
            }else{//偶数+1=奇数
                if(y+1<ji[v[x][i]]){
                    ji[v[x][i]]=y+1;//更新答案
                    q.push(make_pair(v[x][i],y+1));
                }
            }
        }
        q.pop();
    }
}

简要讲解主程序

有了这些主程序应该是很简单的了

int main(){
    int n,m,q;
    read(n);read(m);read(q);
    for(int i=1;i<=m;i++){
        int x,y;
        read(x);read(y);//无向边
        v[x].push_back(y);//连边
        v[y].push_back(x);//连边
    }
    bfw();//跑最短路
    while(q--){
        int x,y;
        read(x);read(y);
        if(y%2==0){
            if(ou[x]>y)puts("No");//如果大于就不可能了
            else puts("Yes");
        }else{
            if(ji[x]>y)puts("No");//如果大于就不可能了
            else puts("Yes");
        }
    }
    return 0;
}

总结

先来看一看这题完整的代码了

#include <bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &FF){
    T RR=1;FF=0;char CH=getchar();
    for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1;
    for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48);
    FF*=RR;
}
template<typename T>void write(T x){
    if(x<0)putchar('-'),x*=-1;
    if(x>9)write(x/10);
    putchar(x%10+48);
}
vector<int>v[100010];
int ji[100010],ou[100010];
void bfw(){//我有一个好朋友叫bfw,所以我写bfs时,喜欢把函数名起为bfw
    memset(ji,0x3f,sizeof(ji));//奇数最短路径
    memset(ou,0x3f,sizeof(ou));//偶数最短路径
    queue<pair<int,int> >q;
    for(int i=0;i<v[1].size();i++){
        ji[v[1][i]]=1;
        q.push(make_pair(v[1][i],1));
    }
    while(q.size()){
        int x=q.front().first,y=q.front().second;
        for(int i=0;i<v[x].size();i++){
            if(y%2==1){//奇数+1=偶数
                if(y+1<ou[v[x][i]]){
                    ou[v[x][i]]=y+1;//更新答案
                    q.push(make_pair(v[x][i],y+1));
                }
            }else{//偶数+1=奇数
                if(y+1<ji[v[x][i]]){
                    ji[v[x][i]]=y+1;//更新答案
                    q.push(make_pair(v[x][i],y+1));
                }
            }
        }
        q.pop();
    }
}
int main(){
    int n,m,q;
    read(n);read(m);read(q);
    for(int i=1;i<=m;i++){
        int x,y;
        read(x);read(y);//无向边
        v[x].push_back(y);//连边
        v[y].push_back(x);//连边
    }
    bfw();//跑最短路
    while(q--){
        int x,y;
        read(x);read(y);
        if(y%2==0){
            if(ou[x]>y)puts("No");//如果大于就不可能了
            else puts("Yes");
        }else{
            if(ji[x]>y)puts("No");//如果大于就不可能了
            else puts("Yes");
        }
    }
    return 0;
}

这道题还是比较有思维含量的,民间数据也出的很好,让我们思考全面。


分享一下我的dijkstra做法

原文链接:http://suo.im/6jM79g

这题是CSPJ2019的第四题。

很遗憾,我考试时还没有学过图论,所以不会做。

但是考场上想到了奇偶性,可因为稍微复杂,为了拿分还是打了暴力。

考场上的分绝对不能轻易放弃!怀着侥幸心理,我在考场上写下了这段代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,q;
int u,v;
int aa,ll;
bool flag;
int a[10005][10005],num[10005];//由于当时不会存图,于是用了一种类似于邻接矩阵的东西来存
/为了防止MLE故意开小
void dfs(int t,int l){//其实不算dfs...只是单纯的扩展。
    //l表示当前生产
    //t代表编号
    if(l==-1)return;//边界条件
    if(flag)return;//flag表示1号点是否提供原材料,算是剪枝吧
    if(t==1&&l==0){//如果1号点提供
        flag=true;//此时有解
        return;
    }
    for(int i=1;i<=num[t];++i){//num代表此点有多少传送带连接
        dfs(a[t][i],l-1);
        //向周围其它的点扩展,索求上一阶段材料
    }
}           
int main(){

    cin>>n>>m>>q;
    for(int i=1;i<=m;++i){
        cin>>u>>v;
        ++num[u];++num[v];
        a[u][num[u]]=v;
        a[v][num[v]]=u;
        //无向图,都存
    }
    for(int i=1;i<=q;++i){
        cin>>aa>>ll;
        flag=false;/初始化(当时因为这个调半天)
        dfs(aa,ll);//爆搜去!
        if(flag)cout<<"Yes"<<endl;//有解
        else cout<<"No"<<endl;//无解
    }
    fclose(stdin);fclose(stdout);//忘了删了=w=
    return 0;
}

然而这个代码肯定会T的,因为写的时候就是为了拿暴力分

爆搜不T数据垃圾

期望得分:40分

luogu数据得分:50分

官方数据得分:40分

在此为机房大佬 @蒻蒻蒻蒻蒻 没删调试白给100分默哀

很遗憾,考后第一次上课就讲起了图论...

于是我左看右看上看下看,

终于理解了链式前向星

学会了单源最短路径

经过大佬们的启发后

发现这题的确和奇偶性有关

算法标签:奇偶性,最短路

分析一下,

本题的实质是:从a到1好点有没有长度为L+1的路径

其实路径不一定要长度为L+1。

因为本题有个神操作就是:我让你提供给我材料你却还来找我要。 好好体会这句话

于是我们的材料就可以在这两点间反复横跳,直到为0。

为什么这么说大家可以参考别的题解或者自己画个图。。。

那么对于此题,我用的是dijkstra(因为只会这个)

其实宽搜和SPFA就可以A掉,只不过我想把dij练得熟练一点。

我们开两个二维数组: \(dis[N][2] \ \And\ vis[N][2]\);

其实和dij模板没什么区别。

开二维就是为了存储1到a号点的奇数路径长度和偶数路径长度

前提是还得小于L,否则够不到

如果该店没有奇数或偶数长度的路径,由于dis数组初值为INF,一定大于L所以不用管

唯一一个毒瘤点需要特判:1号点没有任何传送带时肯定不行啊

CODE:

#include<bits/stdc++.h>
#define ll long long
#define reg register
#define N 1000005

//个人习惯
using namespace std;
int n,m,c,head[N],cnt,dis[N][2];
bool vis[N][2];
struct node{
    int data,dis;//data序号,dis已知最短距离
    bool operator < (const node &a)const{
        return a.dis < dis;//使小根堆顶为目前已知最近的点
    }
};
//重置运算符来给小根堆用

priority_queue<node>q;
//小根堆
struct Edge{
    int to,next;
}e[N];

void add(int u,int v){
    //无权图长度默认为1
    e[++cnt].to = v;
    e[cnt].next = head[u];
    head[u] = cnt; 
}

//链式前向星存图

void Initialization(){
    memset(dis,0x3f,sizeof(dis));
    q.push((node){1,0});
    dis[1][0] = 0;
}

//初始化,先将1点入堆,且dis赋值为INF,而1号点为0。

void dijkstra(){//dijkstra算法
    while(!q.empty()){//只要队列不空
        node tmp = q.top();
        q.pop();//出栈,旧的不去新的不来
        int now = tmp.data;//当前以那个点向周围扩展
        for(int i = head[now]; i ;i = e[i].next){//链式前向星遍历
            int y = e[i].to;
            if(dis[y][0] > dis[now][1] + 1){//奇数走一步为偶数
                dis[y][0] = dis[now][1] + 1;
                if(!vis[y][0]){
                    vis[y][0] = 1;
                    q.push((node){y,dis[y][0]});//入队列
                }
            }
            if(dis[y][1] > dis[now][0] + 1){//偶数走一步为奇数
                dis[y][1] = dis[now][0] + 1;
                if(!vis[y][1]){
                    vis[y][1] = 1;
                    q.push((node){y,dis[y][1]});//入队列
                }
            }
        }
    }
}

int main(){
    cin>>n>>m>>c;
    for(int i = 1 ; i <= m ; ++i ){
        int u,v;
        cin>>u>>v;
        add(u,v);
        add(v,u);//无向图注意存两次
    }
    //无聊的输入
    Initialization();
    dijkstra();
    //这样写清楚一点

    while(c--){
        int a,l;
        cin>>a>>l;
        if(!head[1]){
            puts("No");//如果1号点没有传送带连接
        }
        else if(dis[a][l%2]<=l)puts("Yes");//只要l%2路径<L
        else puts("No");//否则
        //puts自动换行。。。懒得打了
    }
    //return 233;(雾)
}

然后就愉快地AC啦!

posted @ 2020-10-15 20:28  SweepyZhou  阅读(165)  评论(0编辑  收藏  举报