石子游戏(博弈论)(Spaly)

石子游戏

题目大意

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

思路

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

然后这个东西其实是叫做巴什博弈,它的 SG 函数就是 \(x\bmod(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\n");
				lastans++;
			}
			else printf("No\n");
		}
		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;
}

石子游戏(博弈论)(Spaly)

上一篇:寄存器介绍


下一篇:DTU/RTU信号稳定性检测方法