[JOISC2017]細長い屋敷
题目大意:
$n(n\le5\times10^5)$个房间排成一排,相邻两个房间之间有一扇门,第$i$个房间和第$i+1$个房间之间的门可以用第$c_i$种钥匙打开(可能有多个门可以用同一种钥匙打开)。告诉你每个房间有哪几种钥匙。$q(q\le5\times10^5)$次询问,每次询问若一开始在$s$房间,能否到达$t$房间。
思路:
分别用$l[i],r[i]$表示房间$i$能扩展到的最左/最右的房间。
对于每种钥匙用一个数组存一下它出现过的房间位置。枚举每个点$i$,若$c_{l[i]-1}$在$l[i]\sim r[i]$中出现过,则$l[i]-1$能走到的$i$一定能走到,$r[i]+1$同理。不断向左、向右进行扩展。这样总共是$O(n^2\log n)$的,不过因为数据水,跑得比真算法还快。
1 #include<cstdio> 2 #include<cctype> 3 #include<vector> 4 inline int getint() { 5 register char ch; 6 while(!isdigit(ch=getchar())); 7 register int x=ch^'0'; 8 while(isdigit(ch=getchar())) x=(((x<<2)+x)<<1)+(ch^'0'); 9 return x; 10 } 11 const int N=5e5+1; 12 int c[N],l[N],r[N]; 13 std::vector<int> pos[N]; 14 inline bool find(const int &c,const int &l,const int &r) { 15 return *std::lower_bound(pos[c].begin(),pos[c].end(),l)<=r; 16 } 17 int main() { 18 const int n=getint(); 19 for(register int i=1;i<n;i++) c[i]=getint(); 20 for(register int i=1;i<=n;i++) { 21 for(register int j=getint();j;j--) { 22 pos[getint()].push_back(i); 23 } 24 } 25 for(register int i=1;i<=n;i++) { 26 pos[l[i]=r[i]=i].push_back(n+1); 27 } 28 for(register int i=1;i<=n;i++) { 29 for(register int j=0;;j=0) { 30 if(r[i]!=n&&find(c[r[i]],l[i],r[i])) j=r[i]+1; 31 if(l[i]!=1&&find(c[l[i]-1],l[i],r[i])) j=l[i]-1; 32 if(!j) break; 33 l[i]=std::min(l[i],l[j]); 34 r[i]=std::max(r[i],r[j]); 35 } 36 } 37 for(register int i=getint();i;i--) { 38 const int s=getint(),t=getint(); 39 puts(l[s]<=t&&t<=r[s]?"YES":"NO"); 40 } 41 return 0; 42 }