BZOJ5288 [Hnoi2018]游戏
BZOJ5288 [HNOI/AHOI2018]游戏
题解
对于20分,我们可以暴力搞,往返跑,反正怎么都能过。
对于\(x<=y\)的40分的点,就是网上熟悉的倒着跑,就可以AC的做法。
对于一个点,我们处理出它左右可以到达的最大区间。对于这个特殊性质,我们很容易的想到从后往前跑,因为打开它的钥匙一定在前面,那么我们每次访问一个点,跳到它后面序号比它大的那个点的最右区间,看是否能往前扩展,这样每个点的遍历就是\(O(n)\)的了。
然而出题人据说为了卡另一种情况,没卡这一个。
对于满分做法。(口胡的,玄学多好啊
考虑一下对于60分的贪心,我们发现。
因为是从后到前,所以我们不会重复的去处理区间,以此来达到\(O(n)\)。
对于满分,我们也是这样处理,
为了不重复处理区间,对于满分,我们考虑一下拓扑排序,为什么?
我们考虑这样建边,如果钥匙在当前门的左边,\(add(i+1,i)\),右边,\(add(i,i+1)\),表示我们当前无法走到这边,那么我们就可以从底层开始处理出小区间,以此拓展,每个点不会重复访问,时间复杂度为\(O(n)\)。
调了我一晚上qwq,发现一个问题,对于入队,我们优先序号大的会快不少,原因是出题人造的数据基本门和钥匙相等,其实按照这样的建边规则,因为是优先区间小的,然后更新大的,所以其实拓扑的时候还是要记录一下的?
Code
Ps:这个代码是我在洛谷题解上看的,问题很大,但是过了。。。
魔改了一天,想清楚了还是要记录每个点然后在一个一个向上更新,算了不改了。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<queue>
using namespace std;
const int N=1e6+5;
queue<int>q;
int n,m,T,id[N];
int key[N],l[N],r[N],in[N];
int num,head[N];
struct node{
int to,nex;
}e[N<<1];
int read(){
int x=0,w=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
return x*w;
}
void add(int from,int to){
num++;
e[num].to=to;
e[num].nex=head[from];
head[from]=num;
}
int main(){
n=read();m=read();T=read();
for(int i=1;i<=m;i++){
int x=read(),y=read();key[x]=y;
if(x>=y)add(x+1,x),in[x]++;else add(x,x+1),in[x+1]++;
}
for(int i=n;i>=1;i--){
if(!key[i]&&i!=n)id[i]=id[i+1],l[id[i]]=i;
else l[i]=i,r[i]=i,id[i]=i;
}
for(int i=1;i<=n;i++){
l[i]=l[id[i]];r[i]=r[id[i]];
}
for(int i=n;i>=1;i--)if(!in[i])q.push(i);
while(!q.empty()){
int u=q.front(),f=1;q.pop();
for(int i=head[u];i;i=e[i].nex){
{in[e[i].to]--;if(!in[e[i].to])q.push(e[i].to);}
}
while(f){
f=0;
while(l[u]>1&&(!key[l[u]-1]||(key[l[u]-1]>=l[u]&&key[l[u]-1]<=r[u])))
l[u]=l[l[u]-1],f=1;
while(r[u]<n&&(!key[r[u]]||(key[r[u]]>=l[u]&&key[r[u]]<=r[u])))
r[u]=r[r[u]+1],f=1;
}
}
while(T--){
int x=read(),y=read();
if(l[x]<=y&&r[x]>=y)
printf("YES\n");
else printf("NO\n");
}
return 0;
}