zoukankan      html  css  js  c++  java
  • 【题解】【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;
    }
    
  • 相关阅读:
    线性代数思维导图——3.向量
    微分中值定理的基础题型总结
    构造函数
    Python课程笔记(七)
    0241. Different Ways to Add Parentheses (M)
    0014. Longest Common Prefix (E)
    0013. Roman to Integer (E)
    0011. Container With Most Water (M)
    0010. Regular Expression Matching (H)
    0012. Integer to Roman (M)
  • 原文地址:https://www.cnblogs.com/mlystdcall/p/7724378.html
Copyright © 2011-2022 走看看