zoukankan      html  css  js  c++  java
  • 石子游戏(博弈论)(Spaly)

    石子游戏

    题目大意

    给你一棵树,然后树上每一个点有一些石子,然后两个人每人轮流可以选至多 m 个石子移到它所在位置的父亲处,谁没得移谁就输了。
    然后会修改点石子个数和在某个点后加一个有一定石子的儿子点,然后会有询问要输出以某个子树玩当前游戏的 SG 函数。

    思路

    首先你分析一下如果只有两层的结构。
    你分一下推一下就会发现后手要赢是当且仅当 (xmod (m+1)=0)(x) 是石子数)

    然后这个东西其实是叫做巴什博弈,它的 SG 函数就是 (xmod(m+1))
    那你再考虑这个树上的移动,你考虑先讨论线段,再讨论树。

    你推一下发现,要推偶数次的是没有讨论的意义的,相当于没有,因为如果先手挪一定数量的石子,后手可以挪同样数量的式子,然后就又变回偶数,一直挪到终点距离是 (0),先手就输了。
    所以只需要看奇数,在树上就是只需要看深度是偶数的点。

    然后你考虑如何维护,不难想到你可以维护两个数组,一个是奇数层的异或值,一个是偶数层的异或值。
    (我这里是奇数层和全部,可以通过两个异或得到偶数层)
    然后不难想到这些操作要用数据结构维护,然后看到强制在线然后还要加边就考虑用平衡树 Splay。

    然后你发现查询是子树查询,那你考虑在 Splay 上要怎么子树查询,那你是 Splay 维护 dfs 序的数嘛,那你加点的话你就考虑加在它最先的儿子,然后让儿子的 dfs 序跟它父亲一样,所以它就会放在它父亲的后面你就考虑你 dfs 的过程,你其实就是要找从那个点开始 dfs 序往后第一个深度小于等于它的点,那它前面的那些点都是在子树中了。
    那你可以通过维护 Spaly 子树的点的最小深度,找到这个点,然后把它前面的点拎出来,然后就可以查询了。

    代码

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #define ll long long
    
    using namespace std;
    
    struct node {
    	int to, nxt;
    }e[100001];
    int n, m, a[60001], x, y, z;
    int le[60001], KK, lastans;
    int degg[60001], fa[60001], op;
    int dfn[60001], t, aa[60001], deggg[60001];
    char c;
    
    struct SPLAY {
    	int l[120001], r[120001], sz[120001];
    	int deg[120001], rt, mndeg[120001];
    	int Onexor[120001], Allxor[120001];
    	int val[120001], fa[120001];
    	
    	void up(int now) {
    		Allxor[now] = Allxor[l[now]] ^ Allxor[r[now]] ^ val[now];//只用维护奇数和全部,偶数可以通过两个异或得到
    		Onexor[now] = Onexor[l[now]] ^ Onexor[r[now]] ^ ((deg[now] & 1) ? val[now] : 0);
    		mndeg[now] = min(deg[now], min(mndeg[l[now]], mndeg[r[now]]));//维护这个子树中最小深度方便到时找到子树
    		sz[now] = sz[l[now]] + sz[r[now]] + 1;
    	}
    	
    	int build(int lll, int rr, int fath) {
    		if (lll > rr) return 0;
    		int mid = (lll + rr) >> 1;
    		int now = dfn[mid];
    		fa[now] = fath;
    		val[now] = a[mid];
    		deg[now] = degg[mid];
    		l[now] = build(lll, mid - 1, now);
    		r[now] = build(mid + 1, rr, now);
    		up(now);
    		return now;
    	}
    	
    	bool ls(int x) {
    		return l[fa[x]] == x;
    	}
    	
    	void rotate(int x) {
    		int y = fa[x], z = fa[y];
    		int b = ls(x) ? r[x] : l[x];
    		if (z) (ls(y) ? l[z] : r[z]) = x;
    		if (ls(x)) r[x] = y, l[y] = b;
    			else l[x] = y, r[y] = b;
    		fa[x] = z;
    		fa[y] = x;
    		if (b) fa[b] = y;
    		
    		up(y);
    		up(x);
    	}
    	
    	void Splay(int x, int pl) {
    		while (fa[x] != pl) {
    			if (fa[fa[x]] != pl) {
    				if (ls(x) == ls(fa[x])) rotate(fa[x]);
    					else rotate(x);
    			}
    			rotate(x);
    		}
    		if (!pl) rt = x;
    	}
    	
    	int find(int x) {
    		if (mndeg[l[x]] <= deg[rt]) return find(l[x]);//找第一个小于这个深度的,它左边的都是子树内的点
    		if (deg[x] <= deg[rt]) return x;
    		return find(r[x]);
    	}
    	
    	bool query(int x) {
    		Splay(x, 0);
    		int rr = find(r[rt]);
    		Splay(rr, rt);
    		int now = l[rr];//拎出这个区间的点
    		if (deg[rt] & 1) return (Allxor[now] ^ Onexor[now]) != 0;//记得要根据这个的奇偶来看是要看哪个
    			else return Onexor[now] != 0;
    	}
    	
    	void insert(int x, int y, int va) {
    		Splay(x, 0);
    		deg[y] = deg[x] + 1;//新的点的值维护一下,dfn[y]<dfn(x)<dfn[y+1]
    		val[y] = va;
    		fa[y] = x;
    		r[y] = r[x];
    		r[x] = y;
    		fa[r[y]] = y;
    		up(y); up(x);
    	}
    	
    	void change(int x, int va) {
    		Splay(x, 0);
    		val[x] = va;
    		up(x);
    	}
    }T;
    
    int read() {
    	int re = 0; c = getchar();
    	while (c < '0' || c > '9') c = getchar();
    	while (c >= '0' && c <= '9') {
    		re = (re << 3) + (re << 1) + c - '0';
    		c = getchar();
    	}
    	return re;
    }
    
    void add(int x, int y) {
    	e[++KK] = (node){y, le[x]}; le[x] = KK;
    	e[++KK] = (node){x, le[y]}; le[y] = KK;
    }
    
    void dfs(int now, int father) {
    	dfn[++dfn[0]] = now;
    	deggg[now] = deggg[father] + 1;
    	fa[now] = father;
    	for (int i = le[now]; i; i = e[i].nxt)
    		if (e[i].to != father) {
    			dfs(e[i].to, now);
    		}
    }
    
    int main() {
    //	freopen("read.txt", "r", stdin);
    	
    	memset(T.mndeg, 1000000, sizeof(T.mndeg));
    	
    	scanf("%d %d", &n, &m);
    	for (int i = 1; i <= n; i++) {
    		scanf("%d", &aa[i]);
    		aa[i] = aa[i] % (m + 1);
    	}
    	for (int i = 1; i < n; i++) {
    		scanf("%d %d", &x, &y);
    		add(x, y);
    	}
    	
    	scanf("%d", &t);
    	dfs(1, 0);
    	for (int i = 1; i <= n; i++) {
    		a[i] = aa[dfn[i]];
    		degg[i] = deggg[dfn[i]];
    	}
    	dfn[++dfn[0]] = n + t + 1;
    	degg[n + t + 1] = 0;
    	a[n + t + 1] = 0;
    	T.rt = T.build(1, n + 1, 0);
    	
    	while (t--) {
    		op = read();
    		if (op == 1) {
    			x = read() ^ lastans;
    			if (T.query(x)) {
    				printf("Yes
    ");
    				lastans++;
    			}
    			else printf("No
    ");
    		}
    		else if (op == 2) {
    			x = read() ^ lastans;
    			y = read() ^ lastans;
    			T.change(x, y % (m + 1));
    		}
    		else {
    			x = read() ^ lastans;
    			y = read() ^ lastans;
    			z = read() ^ lastans;
    			T.insert(x, y, z % (m + 1));
    		}
    	}
    	
    	return 0;
    }
    
  • 相关阅读:
    Scala伴生对象
    Python之随机森林实战
    DevOps 发展融合运维可视化
    为什么选用 React 创建混合型移动应用?
    查找并修复Android中的内存泄露—OutOfMemoryError
    精华阅读第 12 期 | 最新 App Store 审核指南与10大被拒理由?
    Python 应用剖析工具介绍
    的确,Java存在缺陷。但是……
    APM终端用户体验监控分析(下)
    ASP.NET MVC 应用提速的十种方法
  • 原文地址:https://www.cnblogs.com/Sakura-TJH/p/jzoj_4759.html
Copyright © 2011-2022 走看看