• 【题解】【LibreOJ Beta Round #5】游戏 LOJ 531 基环树 博弈论


    Prelude

    题目链接:萌萌哒传送门♪(^∇^*)


    Subtask 1 & 2

    这是什么鬼题面。。。
    首先要看出,这就是一个基环树博弈。
    具体题意:给出一个基环内向树,一个棋子初始在(1)号节点,双方轮流操作,设棋子所在节点为(u),每次可以从所有指向(u)的节点中选择一个,把棋子移动过去,不能操作者输,问先手是否有必胜策略,或者是否平局。
    但是,这个图是可以变的,每次询问,假如我选择环上的两个点(u)(v),把(u)的出边指向(v),那么游戏的结果如何?
    考虑如果只有一次询问,不修改的话怎么做。
    这么裸的博弈随便做吧?先假设把环上的所有边断开,然后dp求出每个节点的状态,最后再考虑环上的情况,当环上所有点都是必败态的时候就是平局,否则两个人沿着环走,第一个走到必胜节点的人获胜。
    求解一次是(O(n))的,一共(q)次询问,总时间复杂度(O(nq))


    Subtask 3 & 4

    首先我们可以预处理求出离环上每个点最近的必胜点是哪个,我这里用(left_{i})表示。
    每次对原图做修改,相当于把环缩小到原来的环的一个片段。
    假如我们选了环上的(u)(v)两个节点,并且把(u)的出边连接到(v),那么(f_{u})(v)这一段的dp值都可能会改变。
    我们可以用倍增预处理,用(jump[x][s][k])表示假如点(x)的状态为(s),那么(x)之后第(2^{k})个点的状态是什么,这样,查询一个点的状态就是(O(log n))的了。
    对于点(1)不在环上的情况,我们可以直接查询,(O(log n))解决。
    如果点(1)在环上,我们因为有之前预处理的结果,所以只需要求出点(v)的新的dp值((O(log n))),然后大力分类讨论((O(1)))解决。
    总体复杂度就是(O(n log n)),能过87分辣~


    Subtask 5

    LCA告诉我可以线性做,然而我并不会。
    瓶颈是倍增,怎么去掉倍增?怎么降低查询的复杂度?
    我们离线所有的询问,这样就可以用并查集,在(O(n alpha(n)))的时间内处理出所有的“对点(v)的dp值的询问”。
    这样总体复杂度就变成了(O(n alpha(n)))辣~
    因为我比较懒,所以离线询问的时候对询问排了序,复杂度还是(O(n log n))的,但是这个(O(n log n))比倍增的(O(n log n))要小得多了,也是能AC的。


    Conclusion

    为了处理方便,我们通常把环拆开,拆成一条链来处理。
    于是就少不了大量的分类讨论,我写了200行。
    这件事告诉我们,LCA题不可做(╯‵□′)╯︵┻━┻。


    Code

    #include <cstring>
    #include <algorithm>
    #include <cstdio>
    #include <queue>
    #include <cctype>
    
    using namespace std;
    const int MAXN = 1000010;
    const int LOGN = 21;
    int _w;
    
    int read() {
    	int x = 0, ch;
    	while( isspace(ch = getchar()) );
    	do x = x * 10 + ch - '0';
    	while( isdigit(ch = getchar()) );
    	return x;
    }
    
    int n, q, f[MAXN];
    
    int lp[MAXN], lpsz, pos[MAXN]; // lp为环上的每个节点,lpsz为环的大小,pos为原图中的节点在环中的位置
    void find_loop() {
    	int u = 1;
    	while( !pos[u] ) {
    		pos[u] = ++lpsz;
    		lp[lpsz] = u;
    		u = f[u];
    	}
    	int sz = 0;
    	for( int i = pos[u]; i <= lpsz; ++i )
    		lp[++sz] = lp[i];
    	lpsz = sz;
    	for( int i = 1; i <= n; ++i ) pos[i] = 0; // 不在环中的节点pos == 0
    	for( int i = 1; i <= lpsz; ++i )
    		pos[lp[i]] = i;
    }
    
    int pa[MAXN<<1]; // 并查集相关
    int find( int u ) {
    	return pa[u] == u ? u : pa[u] = find(pa[u]);
    }
    
    int dp[MAXN], deg[MAXN], left[MAXN];
    queue<int> bfsq;
    int toend[MAXN][2], to1[MAXN][2]; // to1[x][s]表示当x点的状态为s的时候,点1的状态是什么,toend类似,表示序列上最后一个点的信息
    void prelude() {
    	find_loop();
    	for( int i = 1; i <= n; ++i )
    		++deg[f[i]];
    	for( int i = 1; i <= n; ++i )
    		if( !deg[i] )
    			bfsq.push(i);
    	while( !bfsq.empty() ) {
    		int u = bfsq.front(); bfsq.pop();
    		if( !dp[u] ) dp[f[u]] = 1;
    		if( --deg[f[u]] == 0 )
    			bfsq.push(f[u]);
    	}
    	for( int i = 1; i <= lpsz; ++i )
    		if( dp[lp[i]] ) left[i] = i;
    		else left[i] = left[i-1];
    	for( int i = 1; i < lpsz; ++i ) {
    		pa[i] = i+1+lpsz;
    		pa[i+lpsz] = i+1 + dp[lp[i+1]] * lpsz;
    	}
    	pa[lpsz] = lpsz;
    	pa[lpsz+lpsz] = lpsz+lpsz;
    	for( int i = 1; i <= lpsz; ++i ) {
    		toend[i][0] = find(i) > lpsz;
    		toend[i][1] = find(i+lpsz) > lpsz;
    	}
    	if( pos[1] ) {
    		for( int i = 1; i != pos[1]; ++i ) {
    			pa[i] = i+1+lpsz;
    			pa[i+lpsz] = i+1 + dp[lp[i+1]] * lpsz;
    		}
    		pa[pos[1]] = pos[1];
    		pa[pos[1]+lpsz] = pos[1]+lpsz;
    		for( int i = 1; i != pos[1]; ++i ) {
    			to1[i][0] = find(i) > lpsz;
    			to1[i][1] = find(i+lpsz) > lpsz;
    		}
    		to1[pos[1]][0] = 0;
    		to1[pos[1]][1] = 1;
    	}
    	for( int i = 1; i <= lpsz; ++i ) {
    		pa[i] = i;
    		pa[i+lpsz] = i+lpsz;
    	}
    }
    
    int calc( int t, int v, int p ) {
    	static int right = 1;
    	if( t > p ) { // 把环拆成序列就要大力分类讨论。。。
    		v = toend[t][v];
    		t = 1;
    		v = !v || dp[lp[1]];
    	}
    	if( p == pos[1] )
    		return to1[t][v];
    	while( right < p ) { // 对所有询问按照查询位置排序
    		pa[right] = right+1+lpsz;
    		pa[right+lpsz] = right+1 + dp[lp[right+1]] * lpsz;
    		++right;
    	}
    	return find(t + v*lpsz) > lpsz;
    }
    
    int solve( int l, int r ) {
    	if( !pos[1] ) return dp[1]; // 点1不在环上
    	l = pos[l], r = pos[r];
    	int p = pos[1];
            if( l >= r ) { // 下面都是分类讨论。。。
    		swap(l, r);
    		if( p < l || p > r ) { // 点1不在环上
    			int t = r == lpsz ? 1 : r+1;
    			return calc(t, dp[lp[t]], p);
    		} else {
    			int dpl = dp[lp[l]];
    			if( l > 1 || r < lpsz ) {
    				int t = r == lpsz ? 1 : r+1;
    				dpl = calc(t, dp[lp[t]], l);
    			}
    			if( left[p] >= l+1 ) {
    				int q = left[p];
    				return (p-q+1)&1;
    			} else if( dpl ) {
    				return (p-l+1)&1;
    			} else if( left[r] >= l+1 ) {
    				int q = left[r];
    				return (p-l+1+r-q+1)&1;
    			} else {
    				return 2;
    			}
    		}
    	} else {
    		if( p > l && p < r ) {
    			int t = l+1;
    			return calc(t, dp[lp[t]], p);
    		} else if( p <= l ) {
    			int t = l+1;
    			int dpr = calc(t, dp[lp[t]], r);
    			if( left[p] ) {
    				int q = left[p];
    				return (p-q+1)&1;
    			} else if( left[lpsz] >= r+1 ) {
    				int q = left[lpsz];
    				return (lpsz-q+1+p)&1;
    			} else if( dpr ) {
    				return (p+lpsz-r+1)&1;
    			} else if( left[l] ) {
    				int q = left[l];
    				return (lpsz-r+1+p+l-q+1)&1;
    			} else {
    				return 2;
    			}
    		} else {
    			int t = l+1;
    			int dpr = calc(t, dp[lp[t]], r);
    			if( left[p] >= r+1 ) {
    				int q = left[p];
    				return (p-q+1)&1;
    			} else if( dpr ) {
    				return (p-r+1)&1;
    			} else if( left[l] ) {
    				int q = left[l];
    				return (p-r+1+l-q+1)&1;
    			} else if( left[lpsz] >= r+1 ) {
    				int q = left[lpsz];
    				return (p-r+1+l+lpsz-q+1)&1;
    			} else {
    				return 2;
    			}
    		}
    	}
    }
    
    int qu[MAXN], qv[MAXN]; // 存放询问的地方
    int rk[MAXN], ans[MAXN];
    
    bool cmp_qv( int i, int j ) { // 对询问按照点在序列上的位置排序
    	i = pos[qv[i]], j = pos[qv[j]];
    	return i < j;
    }
    
    int main() {
    	n = read(), q = read();
    	for( int i = 1; i <= n; ++i )
    		f[i] = read();
    	prelude();
    	for( int i = 0; i < q; ++i )
    		qu[i] = read(), qv[i] = read(), rk[i] = i;
    	sort(rk, rk+q, cmp_qv);
    	for( int i = 0; i < q; ++i )
    		ans[rk[i]] = solve(qu[rk[i]], qv[rk[i]]);
    	for( int i = 0; i < q; ++i )
    		printf( "%d
    ", ans[i] );
    	return 0;
    }
    
  • 相关阅读:
    Java8 Comparator 排序方法
    IDEA自动清理优化import包
    图灵的文章“Computing machinery and intelligence”译文
    NLP第一课(我也是才开始学)
    java面试基础篇(一)
    python基本排序算法
    python基础面试题整理---从零开始 每天十题(04)
    python基础面试题整理---从零开始 每天十题(03)
    python基础面试题整理---从零开始 每天十题(02)
    python基础面试题整理---从零开始 每天十题(01)
  • 原文地址:https://www.cnblogs.com/mlystdcall/p/7724378.html
Copyright © 2020-2023  润新知