【LOJ2397】「JOISC 2017 Day 3」幽深府邸(奇怪题)
- 有 \(n\) 个房间,第 \(i\) 个房间有 \(b_i\) 种钥匙 \(v_{i,1},v_{i,2},\cdots,v_{i,b_i}\),第 \(i\) 个房间和第 \(i+1\) 个房间的通道需要第 \(a_i\) 种钥匙打开(钥匙可以重复使用)。
- \(q\) 次询问,每次求从 \(x\) 出发能否到达 \(y\)。
- \(2\le n\le5\times10^5\),\(1\le q,\sum b_i\le5\times10^5\),
解题思路
设从 \(i\) 出发能到达的房间范围为 \([L_i,R_i]\)。
事先预处理 \(A_i\) 表示想要走 \(i\) 和 \(i+1\) 间的通道至少需要满足能到达 \([A_i,i]\),\(B_i\) 表示想要走 \(i\) 和 \(i+1\) 间的通道至少需要满足能到达 \([i+1,B_i]\)。
我们从左向右求解答案,初始设 \(L_i=R_i=i\),如果 \(B_{L_{i}-1}\le R_i\) 就能将 \(L_i\) 向左更新,如果 \(B_{R_i}\ge L_i\) 就能将 \(R_i\) 向右更新。
向左更新的时候,若 \(R_{L_i-1}\ge i\) 说明 \(i\) 与 \(L_i-1\) 的答案相同可以直接复制过来,否则更新 \(L_i\) 为 \(L_{L_i-1}\)。容易发现这其实是一个隐式单调栈的过程。
向右更新的时候,我们无法从谁那里找答案,但考虑到在 \(L_i\) 固定的情况下能向右更新的条件确定为 \(B_{R_i}\ge L_i\),可以倍增/二分找到能更新到的最大位置(其实向左更新的时候也可以倍增/二分,但没必要)。
因此一轮更新要把两种方向都讨论一遍,直至两种方向都无法再更新才结束。
代码:\(O(n\log n)\)
#include<bits/stdc++.h>
#define Cn const
#define CI Cn int&
#define N 500000
#define LN 20
using namespace std;
namespace FastIO
{
#define FS 100000
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
char oc,FI[FS],*FA=FI,*FB=FI;
Tp void read(Ty& x) {x=0;while(!isdigit(oc=tc()));while(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
Ts void read(Ty& x,Ar&... y) {read(x),read(y...);}
}using namespace FastIO;
int n,a[N+5],lst[N+5],A[N+5],B[N+5],Mn[N+5][LN+1],L[N+5],R[N+5];vector<int> v[N+5];
int J(int l,int x) {int i;for(i=0;x+(1<<i)-1<=n&&Mn[x][i]>=l;++i);for(--i;~i;--i) Mn[x][i]>=l&&(x+=1<<i);return x;}//倍增,只要满足R[x]≥l就向右
int main()
{
int Qt,i,j,x,y;for(read(n),i=1;i^n;++i) read(a[i]);
for(i=1;i<=n;++i) {for(read(x);x;--x) read(y),v[i].push_back(y),lst[y]=i;A[i]=lst[a[i]];}//求A
for(i=1;i<=n;++i) lst[i]=n+1;for(i=n;i^1;--i) {for(auto x:v[i]) lst[x]=i;B[i-1]=lst[a[i-1]];}//求B
for(i=n;i;--i) for(Mn[i][0]=A[i],j=1;i+(1<<j)-1<=n;++j) Mn[i][j]=min(Mn[i][j-1],Mn[i+(1<<j-1)][j-1]);//倍增预处理
int fg;for(i=1;i<=n;++i) for(L[i]=R[i]=i,fg=1;fg;)
{
fg=R[i],R[i]=J(L[i],R[i]),fg=fg!=R[i];//向右更新
if(L[i]^1&&R[i]>=B[L[i]-1]) {if(R[L[i]-1]>=i) {R[i]=R[L[i]-1],L[i]=L[L[i]-1];break;}L[i]=L[L[i]-1],fg=1;}//向左更新
}
read(Qt);while(Qt--) read(x,y),puts(L[x]<=y&&y<=R[x]?"YES":"NO");return 0;//判断y是否在x能到达的范围内
}
待到再迷茫时回头望,所有脚印会发出光芒