游戏「AHOI / HNOI2018」

题意

(n)个房间,第(i)个房间与第(i+1)个房间之间有一扇门,有些门上了锁。已知所有上了锁的门的编号,以及每一把锁在哪个房间,有多组询问:能否从(a)房间到达(b)房间?

思路

考虑优化暴力。

对于每一个节点,维护L[i],R[i],表示该节点能够到达最左右极点。

对于任意一扇上了锁的门,如果钥匙在门的左边,那么这扇门右边的房间肯定都到不了左边的房间,而左边的房间有可能达到右边的房间。如果钥匙在门的右边则同理。

也就是说,对于一扇上了锁的门,右边的每一个节点的[L,R]都不可能包含左边的节点的[L,R],而反过来是有可能的。

可以考虑这样的一种拓展顺序:

对于拓展序列中的每一个节点,位于它前面的任意节点都不可能包含它的区间。

显然这样的拓展顺序是最优的,因为我们的更新始终不会被干扰。

为了得到这样的拓展顺序,考虑采用拓扑排序。对于钥匙在门左侧的情况,从右边连向左边的节点,反之亦然。然后跑一遍拓扑排序即可得到我们需要的拓展顺序,最后暴力拓展即可。

但是这样写还是会T,所以需要进一步优化。

考虑这样一种情况:只有一个门上了锁,而其他所有门都没有上锁。

对于这样的情况,我们的程序仍然有n的计算量,而事实上只需要1的计算量。

可以对数据进行预处理,将不要钥匙就可以直接到达的点锁在一起,然后对缩完点的序列再进行拓扑排序,这样对于上述情况就可以做的高效处理了。


代码

无缩点环节,70分代码

#include <bits/stdc++.h>

using namespace std;

namespace StandardIO {

	template<typename T> inline void read (T &x) {
		x=0;T f=1;char c=getchar();
		for (; c<'0'||c>'9'; c=getchar()) if (c=='-') f=-1;
		for (; c>='0'&&c<='9'; c=getchar()) x=x*10+c-'0';
		x*=f;
	}
	template<typename T> inline void write (T x) {
		if (x<0) putchar('-'),x=-x;
		if (x>=10) write(x/10);
		putchar((x%10)+'0');
	}

}

using namespace StandardIO;

namespace Solve {
	
	const int N=1000010;
	
	int n,m,p;
	int key[N],l[N],r[N],indeg[N];
	int cnt;
	int head[N];
	struct node {
		int to,next;
	} edge[N<<1];
	int num;
	int seq[N];
	
	inline void add (int a,int b) {
		edge[++cnt].to=b,edge[cnt].next=head[a],head[a]=cnt,++indeg[b];
	}
	inline void topsort () {
		queue<int> q;
		for (register int i=1; i<=n; ++i) if (!indeg[i]) q.push(i);
		while (!q.empty()) {
			int cur=q.front();q.pop(),seq[++num]=cur;
			l[cur]=r[cur]=cur;
			for (register int i=head[cur]; i; i=edge[i].next) {
				int to=edge[i].to;
				--indeg[to];
				if (!indeg[to]) q.push(to);
			}
		}
	}

	inline void Main () {
		read(n),read(m),read(p);
		for (register int i=1; i<=m; ++i) {
			int x,y;
			read(x),read(y);
			key[x]=y;
			if (y>x) add(x,x+1);
			else add(x+1,x);
		} 
		topsort();
		int counter=0;
		for (register int i=1; i<=n; ++i) {
			int now=seq[i];
			while (1) {
				int tl=l[now],tr=r[now];
				while (tl>1&&(!key[tl-1]||(tl<=key[tl-1]&&key[tl-1]<=tr))) tl=l[tl-1];
				while (tr<n&&(!key[tr]||(tl<=key[tr]&&key[tr]<=tr))) tr=r[tr+1];
				if (tl==l[now]&&tr==r[now]) break;
				l[now]=tl,r[now]=tr;
			}
		}
		for (register int i=1; i<=p; ++i) {
			int s,t;
			read(s),read(t);
			if (l[s]<=t&&t<=r[s]) puts("YES");
			else puts("NO");
		}
	}

}

int main () {
	Solve::Main();
}

原文地址:https://www.cnblogs.com/ilverene/p/11206029.html