题解 P5663 【加工零件】

· · 题解

前言:这篇文章只讲暴力,没有SPFA,没有图论!!!

关于搜索,它没有死!这题搜索可以拿到80pts!

一般,考场上写暴力的话能写出40pts的代码,只有前8个点

经过一些记忆化处理,可以骗到60pts

再经过一些对memset毒瘤处理,可以再骗走20pts

总体思路:牺牲空间换取时间

一.记忆化搜索60pts

用rec[u][l]表示工人u加工l阶段是否需要编号为1的人提供原料
需要:rec[u][l]=1;不需要rec[u][l]=-1;不确定:rec[u][l]=0;

观察数据可知本题询问q较大,所以每次完成一次询问后,就记录一次rec[u][l]
测评结果

参考代码:

#include<bits/stdc++.h>
#define r register
using namespace std;

int read(){
    int res=0,f=1;char ch;
    while(isspace(ch=getchar()));
    if(ch=='-') f=-1,ch=getchar();
    do{
        res=res*10+ch-'0';
    }while(isdigit(ch=getchar()));
    return res*f;
}

const int maxn=5555;
int cnt,head[maxn],n,m,q,rec[maxn][maxn],sure;
bool used[maxn];

struct edge{
    int u,v,next;
}e[maxn];

inline void adde(int x,int y){
    e[++cnt].u=x;
    e[cnt].v=y;
    e[cnt].next=head[x];
    head[x]=cnt;
}

void dfs(int now,int depth){
    if(sure==1) return;
    if(rec[now][depth]==1){//需要worker1
        sure=1;return;//打标记
    }
    if(rec[now][depth]==-1) return;//不需要返回即可
    if(depth==1) {
        for(r int i=head[now];i;i=e[i].next)
            if(e[i].v==1){sure=1;break;} 
        if(sure!=1) rec[now][depth]=-1; 
        return; 
    }
    for(r int i=head[now];i;i=e[i].next){//遍历每一条边
        dfs(e[i].v,depth-1);
    }
}

int main(){   
    n=read(),m=read(),q=read();
    int u,v;
    for(r int i=1;i<=m;i++){
        u=read(),v=read();
        adde(u,v);
        adde(v,u); 
    }
    int l;
    for(r int k=1;k<=q;k++){
        u=read(),l=read();
        sure=-1;dfs(u,l);
        if(sure==1) printf("Yes\n");
        else printf("No\n");
        rec[u][l]=sure;//记忆化
    }

    return 0;
}

二.算法改进80pts

不难发现:我们在搜索时会搜到很多相同的(u,l);
所以,我们在解决一个询问时,每当dfs(u,l)后就记录一个vis[u][l]=1; 进入下一个询问时再将vis[][]清零即可。
这样必定会节省很多时间。

但是,问题来了:由于题中有很多个询问,怎样高效的清零呢?

有大佬说:memset不就行了
很不幸的是,测评结果将 会出人意料
TLE飞了
但是发现原来TLE的10#,11#,12#现在AC了;
原来AC的13#,14#,16#现在TLE了,并且每一个点的时间大幅增大

不对啊,优化后效率反而更低,这是为什么呢
其实,原因就在memset

memset(vis,0,sizeof(vis));

由于vis是个n*n的二维数组,n又很大,所以每次清零耗费了相当多的时间

所以:改成循环呢?
更是只有45pts

难道就没有任何办法吗???
有!!!
我们想办法让清零次数最少,那每次就只把改成1的赋值为0
(由于是bool数组,把1改成0,可以用取反"!",常数优化)

我们执行vis[i][j]=1;后,把i和j都用数组存下来
就像这样

for(r int i=head[now];i;i=e[i].next){
    dfs(e[i].v,depth-1);
    vis[e[i].v][depth-1]=1;

    ++cntmt;
    xfkmt[cntmt]=e[i].v;
    yfkmt[cntmt]=depth-1;
}

这样,我们在清零时直接:

for(r int i=1;i<=cntmt;i++)
    vis[xfkmt[i]][yfkmt[i]]=!vis[xfkmt[i]][yfkmt[i]];

也就是:只清零有改动的数

然后,818MS轻松AC前16个点!最后4个二维数组根本没法存!

测评结果

三.参考程序

#include<bits/stdc++.h>
#define r register
using namespace std;

int read(){
    int res=0,f=1;char ch;
    while(isspace(ch=getchar()));
    if(ch=='-') f=-1,ch=getchar();
    do{
        res=res*10+ch-'0';
    }while(isdigit(ch=getchar()));
    return res*f;
}

const int maxn=2e4;
int cnt,n,m,q,sure,cntmt;
int head[maxn],rec[maxn][maxn],xfkmt[maxn],yfkmt[maxn];
bool vis[maxn][maxn];

struct edge{
    int u,v,next;
}e[maxn];

inline void adde(int x,int y){
    e[++cnt].u=x;
    e[cnt].v=y;
    e[cnt].next=head[x];
    head[x]=cnt;
}

void dfs(int now,int depth){
    if(sure==1) return;
    if(rec[now][depth]==-1||vis[now][depth]) return;
    if(rec[now][depth]==1){
        sure=1;return;
    }
    if(depth==1) {
        for(r int i=head[now];i;i=e[i].next)
            if(e[i].v==1){sure=1;break;} 
        if(sure!=1) rec[now][depth]=-1; 
        return; 
    }
    for(r int i=head[now];i;i=e[i].next){
        dfs(e[i].v,depth-1);
        vis[e[i].v][depth-1]=1;

        ++cntmt;
        xfkmt[cntmt]=e[i].v;
        yfkmt[cntmt]=depth-1;
    }
}

int main(){   
    n=read(),m=read(),q=read();
    int u,v;
    for(r int i=1;i<=m;i++){
        u=read(),v=read();
        adde(u,v);
        adde(v,u); 
    }
    int l;
    for(r int k=1;k<=q;k++){
        u=read(),l=read();
        cntmt=0;sure=-1;
        dfs(u,l);   
        if(sure==1) printf("Yes\n");
        else printf("No\n");
        rec[u][l]=sure;
        for(r int i=1;i<=cntmt;i++)
            vis[xfkmt[i]][yfkmt[i]]=!vis[xfkmt[i]][yfkmt[i]];
    }

    return 0;
}