题解 洛谷P6560 [SBCOI2020] 时光的流逝
题解 洛谷P6560 [SBCOI2020] 时光的流逝
题目大意
给定一个\(n\)个点,\(m\)条边的有向图(不保证无环)。\(q\)次询问。每次指定一组起点和终点,并在起点处放一枚棋子。
有两个游戏玩家,轮流移动棋子(只能顺着图上的边移动,每人每次只能移动一步)。
先将棋子移动到终点的人立即获胜。如果谁无法继续移动了,那么他失败,对手获胜。
问先、后手是否有必胜策略。如果先手有必胜策略输出\(1\),如果后手有必胜策略输出\(-1\),如果两人都没有必胜策略输出\(0\)。
数据范围:\(1\leq n\leq 10^5\),\(1\leq m\leq 5\times 10^5\),\(1\leq q\leq 500\)。保证起点和终点不同。
本题题解
考虑,给出的图如果是一个DAG(无环),我们可以对反图做拓扑排序,同时推出答案。在反图上,所有入度为\(0\)的点,都是先手必败;询问给定的终点也是先手必败(因为距离它为\(1\)的点肯定是先手必胜了,所以可以把它理解为先手必败)。然后对于其他点,如果存在至少一个能到达它的点,是先手必败的,那么它先手必胜;否则它是先手必败。这样递推一下就能求出答案了,时间复杂度是\(O(qm)\)的。
再考虑有环的情况。有环和无环最大的区别是,正常的拓扑排序时,我们无法进入到环里。那么一个环,就可能“隔绝”一些已知的答案。具体来说,对于某个节点:
- 如果(在反图上)至少存在一个能到达它的点是先手必败的,那该节点一定是先手必胜的。无论它在不在环上,入度是否为\(0\),我们都把它加入到队列里,并且之后不再访问它(一个点不能入队两次)。
- 如果没有发现,至少一个,能到达它的、先手必败的点。那么我们看当前节点入度是否已经清零。如果已经清零,说明它不在环上,而且我们已经考虑过了所有能到达它的边,因此可以直接断定它是先手必败的,然后加入队列即可。
- 如果它的入度还没有清零,说明它一定在某个环上。这种情况下先、后手都可以在环上无限地绕圈。所以此时该节点的状态是未知的。
整个过程,和普通的拓扑排序还是很类似的。主要的区别是,一旦确定了一个节点的状态(必胜或必败),就立即加入到队列中,并且从此不再访问它。这样可以避免“环”对传递答案造成的不必要的“隔绝”。根据这个原则,我们初始时也会把终点加入到队列中,无论它入度是否为\(0\),因为我们前面说过,终点一定是先手必败的。
时间复杂度\(O(qm)\)。
参考代码:
//problem:P6560
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
template<typename T>inline void ckmax(T& x,T y){x=(y>x?y:x);}
template<typename T>inline void ckmin(T& x,T y){x=(y<x?y:x);}
const int MAXN=1e5,MAXM=5e5;
struct EDGE{int nxt,to;}edge[MAXM+5];
int head[MAXN+5],tot;
inline void add_edge(int u,int v){edge[++tot].nxt=head[u],edge[tot].to=v,head[u]=tot;}
int n,m,q,in_degree[MAXN+5],cur_deg[MAXN+5],st,ed,f[MAXN+5];
int main() {
cin>>n>>m>>q;
for(int i=1;i<=m;++i){
int u,v;
cin>>u>>v;
add_edge(v,u);//反向边
in_degree[u]++;
}
for(int tq=1;tq<=q;++tq){
cin>>st>>ed;
queue<int>que;
for(int i=1;i<=n;++i){
cur_deg[i]=in_degree[i];
if(!cur_deg[i] || i==ed)
f[i]=-1,que.push(i);
else
f[i]=0;
}
while(!que.empty()){
int u=que.front(); que.pop();
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].to;
if(f[v]!=0)continue;
cur_deg[v]--;
if(f[u]==-1){
f[v]=1;
que.push(v);
}
else if(!cur_deg[v]){
if(f[v]!=1)
f[v]=-1;
que.push(v);
}
}
}
cout<<f[st]<<endl;
}
return 0;
}