1. 程式人生 > 實用技巧 >題解 洛谷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;
}