长长的屋子

题目描述

小林要搬家啦!
小林把家搬到了一个长长的屋子里,屋子里一共有N个房间连成一排,从左到右依次标号为1…N。每两个相邻的房间之间都有一扇门,门上有一把锁,第i个房间和第i+1个房间之间的锁类型为Ci。为了防止康娜胡闹,小林把钥匙都藏在了房间里,第i个房间里放了Bi把钥匙,钥匙的种类分别为Ai[1],Ai[2],…,Ai[Bi]。注意,每把钥匙只能打开对应类型的锁,同一种类型可能存在多把钥匙和多把锁,钥匙开锁后不会消失。
但是这样仍然不能阻止康娜胡闹,因此,小林会在康娜睡着的时候把康娜移动到别的房间里去。在接下来的Q天里,第i天,康娜醒来的时候会发现自己身处第Xi个房间里,而她想要到第Yi个房间里去。
康娜不知道自己能不能到达第Yi个房间,请你帮助她计算出,她能否在第Xi个房间出发,拾取到达房间的所有钥匙并打开对应的锁,最终到达第Yi个房间。

记忆化搜索

设A[i]表示从i出发能到达的区间。
如果i能到达j,有A[i]包含A[j]。
如果i和j能互达,有A[i]=A[j]。
预处理开每扇门所需要钥匙在这扇门往左往右的第一个房间。
这样可以快速判断已有一个区间的钥匙能不能开某扇门。
接下来记忆化搜索。我们用并查集表示A相同的点。
假如在搜索j,拓展到k。
若k已经搜过了,A[j]=A[j] or A[k]。
若k已经搜到了,我们用并查集缩j和k并退出当前搜索。
若k未搜过,我们搜索k。
这样的复杂度是对的,因为任意一扇门只会开一次。

#include<cstdio>
#include<algorithm>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
const int maxn=500000+10;
int left[maxn],right[maxn],last[maxn],c[maxn];
int h[maxn],go[maxn],next[maxn];
int fa[maxn],sta[maxn],L[maxn],R[maxn];
bool bz[maxn],pd[maxn];
int i,j,k,l,r,t,n,m,tot,top,cnt,x,y;
int read(){
    int x=0,f=1;
    char ch=getchar();
    while (ch<'0'||ch>'9'){
        if (ch=='-') f=-1;
        ch=getchar();
    }
    while (ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
void add(int x,int y){
    go[++tot]=y;
    next[tot]=h[x];
    h[x]=tot;
}
int getfa(int x){
    return fa[x]?fa[x]=getfa(fa[x]):x;
}
void merge(int x,int y){
    int a=getfa(x),b=getfa(y);
    if (a==b) return;
    L[b]=min(L[b],L[a]);
    R[b]=max(R[b],R[a]);
    fa[a]=b;
}
int main(){
    freopen("long.in","r",stdin);freopen("long.out","w",stdout);
    n=read();
    fo(i,1,n-1) c[i]=read();
    fo(i,1,n){
        k=read();
        fo(j,1,k){
            t=read();
            add(i,t);
        }
        L[i]=R[i]=i;
    }
    fo(i,1,n) last[i]=n+1;
    fd(i,n,1){
        if (i<n) right[i]=last[c[i]];
        t=h[i];
        while (t){
            last[go[t]]=i;
            t=next[t];
        }
    }
    fo(i,1,n) last[i]=0;
    fo(i,1,n){
        if (i>1) left[i]=last[c[i-1]];
        t=h[i];
        while (t){
            last[go[t]]=i;
            t=next[t];
        }
    }
    fo(i,1,n)
        if (!bz[getfa(i)]){
            x=getfa(i);
            sta[top=1]=x;
            pd[x]=1;
            while (top){
                x=getfa(sta[top]);
                if (L[x]>1&&right[L[x]-1]<=R[x]) y=getfa(L[x]-1);
                else if (R[x]<n&&left[R[x]+1]>=L[x]) y=getfa(R[x]+1);
                else{
                    pd[x]=0;
                    bz[x]=1;
                    top--;
                    continue;
                }
                if (bz[y]){
                    L[x]=min(L[x],L[y]);
                    R[x]=max(R[x],R[y]);
                }
                else if (pd[y]){
                    merge(y,x);
                    top--;
                }
                else{
                    sta[++top]=y;
                    pd[y]=1;
                }
            }
        }
    m=read();
    while (m--){
        j=read();
        k=read();
        j=getfa(j);
        if (L[j]<=k&&k<=R[j]) printf("YES\n");else printf("NO\n");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值