石子游戏(博弈论)(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;
}