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

石子游戏

题目大意

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

思路

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

然后这个东西其实是叫做巴什博弈,它的 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\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; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/jzoj_4759.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(257)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示