zoukankan      html  css  js  c++  java
  • 题解 洛谷P6560 [SBCOI2020] 时光的流逝

    题目大意

    题目链接

    给定一个(n)个点,(m)条边的有向图(不保证无环)。(q)次询问。每次指定一组起点和终点,并在起点处放一枚棋子。

    有两个游戏玩家,轮流移动棋子(只能顺着图上的边移动,每人每次只能移动一步)。

    先将棋子移动到终点的人立即获胜。如果谁无法继续移动了,那么他失败,对手获胜。

    问先、后手是否有必胜策略。如果先手有必胜策略输出(1),如果后手有必胜策略输出(-1),如果两人都没有必胜策略输出(0)

    数据范围:(1leq nleq 10^5)(1leq mleq 5 imes 10^5)(1leq qleq 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;
    }
    
  • 相关阅读:
    PCI配置空间与IO空间与内存空间
    python读配置文件,根据配置文件内容改写二进制文件
    python参数的传递机制
    python复制、移动文件到指定文件夹
    python解析配置文件
    python struct用法
    Python 字符串前面加u,r,b的含义
    shell算数运算符
    三、shell -break、continue、exit、return
    shell-逻辑判断
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/13329521.html
Copyright © 2011-2022 走看看