一类SG函数递推性质的深入分析——2018ACM陕西邀请赛H题
题目描述
定义一种有根二叉树\(T(n)\)如下:
(1)\(T(1)\)是一条长度为\(p\)的链;
(2)\(T(2)\)是一条长度为\(q\)的链;
(3)\(T(i)\)是一棵二叉树,它的左子树是\(T(i-2)\),右子树是\(T(i-1)\)。
现在给定\(p,q,n\),现在Alice和Bob在树\(T(n)\)上玩游戏,每人轮流从树上拿掉一棵子树,直到有一个玩家拿掉根结点所在的子树为止(那么该玩家输了)。现在问先手在第一轮有多少种拿掉子树的方法,可以保证之后自己一定能赢。只用输出答案最后18位即可。
样例:
1 2 5
1 2 10
输出:
1
17
数据范围:保证\(n,p,q \le 2000\)。
解题思路
SG函数的求解
该问题等价于不能拿掉总的根,无法操作者输。关于该问题显然应从SG理论入手。下面推导一般树意义下的SG函数。
引理1:设树\(T\)只有一个儿子,即具有子树\(T_1\),则\(SG(T)=SG(T_1)+1\)。
证明:利用数学归纳法,对树的结点进行归纳。
初始:当\(T\)只有2个结点时,显然成立。
归纳:设\(take(T)\)为\(T\)去掉一棵子树后所有可能的树形态的集合。根据SG基本定理, \[SG(T)=mex(\{T' \in take(T):SG(T')\})\] 利用归纳性质, \[SG(T)=mex(\{0\} \cup \{T' \in take(T_1):SG(T')+1\})=SG(T_1)+1\]
证毕。
引理2:设树\(T\)具有子树\(T_1,\cdots,T_k\),则\(SG(T)=\mathop \oplus \limits_{1 \le j \le k} (SG({T_j}) + 1)\)。
证明:对于树\(T\)的游戏,显然等价于只有子树\(T_1,\cdots,T_k\)的游戏之和,之间互不影响,因为不到最后一步任何人不会拿掉总的树根。根据引理1即得上式。
根据以上引理,本题中便可得出\(SG[i]=(SG[i-1]+1)\oplus (SG[i-2]+1)\),即可在\(O(n)\)时间复杂度求出原问题中每个\(T(i)(i\le n)\)的SG值。
转化为动态规划问题
如果原问题问谁赢那么就已经做完了。然而现在问的是第一个人第一步有几种能赢的方案,也就是说有几种拿走子树的方案使得之后SG为0。
考虑动态规划。设\(dp[i][j]\)表示在树\(T(i)\)上玩游戏,先手拿走一棵子树后SG值变为\(j-1\)的方案数(这里\(j-1\)是为了后续方便),\(dp[i][0]\)恒定义为1。
初始化:对任意\(1\le j < p\),\(dp[1][j]=1\);对任意\(1\le j < q\),\(dp[2][j]=1\);
转移:对于\(i>2\)的情形,必然从其中一棵子树中选择拿掉一棵子树。以拿掉左子树中的一棵子树为例:此时右子树SG值不变,为了使之后原树SG变为\(j-1\),必须使左子树SG值变为\(((j-1)\oplus (SG[i-1]+1))-1\)。特别地,当\((j-1)\oplus (SG[i-1]+1)\)为0时,表示必须拿掉左子树。这样可得转移方程: \[dp[i][j]=dp[i-2][(j-1)\oplus (SG[i-1]+1)]+dp[i-1][(j-1)\oplus (SG[i-2]+1)]\] 最终答案为\(dp[n][1]\)。这样我们便在状态数的时间复杂度内解决了本问题。
状态数的分析
问题关键在于确定\(dp[i][j]\)中\(j\)到底需要计算到多少。考虑所有\(SG[i](i \le n)\)中二进制位数的最大值(设为\(k\)),那么只需计算\(j < 2^k\)即可。这是因为只要\(j < 2^k\),则转移过程中的\((j+1) \oplus SG[i-1]]\)以及\((j+1) \oplus SG[i-2]\)也必然小于\( 2^k\)。所以现在关键问题在于计算\(SG[i](i \le n)\)中二进制位数的最大值到底是多少。事实上,推导这一问题较为复杂,将留在下一节专门讨论,同时将会得到许多有用的性质。(PS:赛场上我大力猜了一波\(SG[i]<2^{13}=8192\),然后就AC了2333)
关于异或递推式
以下以本题中所用递推式为例(令\(a[i]=SG[i]+1\)变形为\(a[i]=(a[i-1]\oplus a[i-2])+1\)),讨论异或递推式的奇妙性质。
引理3:对任意正整数\(k\),\(a[i] \mod 2^k\)具有循环节。
证明:由于\(a[i] \mod 2^k\)只有有限种取值,故必存在\(x<y\),使得
\[a[x] \equiv a[y] \mod 2^k,a[x+1] \equiv a[y+1] \mod 2^k\]
那么根据递推式必有(对任意\(z>0\))
\[a[x+z] \equiv a[y+z] \mod 2^k\]
另一方面,\(a[i-2]=a[i-1]\oplus (a[i]-1)\),故上式对\(z<0\)也成立。
故\(y-x\)是循环节。证毕。
引理4:\(a[i] \mod 2\)的循环节必为1或3,且循环节为1时数列是有界的。
证明:只需按a[1]和a[2]的值枚举即可:
情况1:001001......循环节为3;
情况2:010010......循环节为3;
情况3:100100......循环节为3;
情况4:1111111......循环节为1,此时递推式中的+1永远不进位,而\(a[4]\)的高位\(=a[3]\)的高位异或\(a[2]\)的高位\(=(a[2]\)的高位异或\(a[1]\)的高位\()\)异或\(a[2]\)的高位\(=a[1]\)的高位,故最终推出\(a[i]=a[i \mod 3]\)。证毕。
定理5:当\(a[i] \mod 2\)的循环节为3时,\(a[i] \mod 2^k\)的循环节为\(3 \times 2^{k-1}\)。
证明:对\(k\)应用数学归纳法,关键在于考虑低\(k-1\)位向高位的进位。设想若低\(k-1\)位不进位,第\(k\)位循环节必然是1或3(类似引理4);只有低位进位才会打乱高位的循环节。
基础:\(k=2\)时,通过枚举\(a[1],a[2]\)模4的余数取值可得所有情况,事实上只有2种:
0232210(进位)23221……循环节为6,中间有1次进位;
00120(进位)30(进位)0(进位)120(进位)3 ……循环节为6,中间有3次进位,且其中两次进位的位置模3余数相同;
归纳:设\(a[x]_k\)表示\(a[x]\)第\(k\)位。考虑在位置\(x\)低\(k-1\)位向第\(k\)位有一次进位。这将导致\(a[x]_k\)取反,进一步导致\(a[x+1]_k\)取反,\(a[x+2]_k\)不变,这种取反是3个一循环的。
直到低\(k-1\)位的下一周期,\(a[x+3 \times 2^{k-2}]_k\)再次被取反从而不变了,之后\(a[x+1+3 \times 2^{k-2}]_k\)也不变……这样一来,\(a[x]\)和\(a[x+3 \times 2^{k-2}]\)第\(k\)位必然不同。进一步地,设\(x \mod 3=i\),则对于所有\(j\),\(a[3j+i]\)和\(a[3j+i+3 \times 2^{k-2}]\)第\(k\)位必然不同。
对于基础里的两种情况,无论是有一次进位,还是有3次进位但两次位置模3余数相同(从而抵消了),均可以保证\(a[3j+i]_k \neq a[3j+i+3 \times 2^{k-2}]_k\)。从而\(a[i] \mod 2^k\)的循环节为\(3 \times 2^{k-1}\)。
接下来再得到第\(k\)位向\(k+1\)位一个循环内的进位情况。由于对于位置\(x\)和\(x+3 \times 2^{k-2}\)低\(k-1\)位向第\(k\)位分别有一次进位,而\(a[x]_k\)和\(a[x+3 \times 2^{k-2}]_k\)又不同,故必有且仅有一次使得低\(k\)位向第\(k+1\)位进位。因此在一个周期\(3 \times 2^{k-1}\)内,一共向第\(k+1\)位的进位次数仍满足基础中的两种情况(进位1次或进位3次但有两次位置模3余数相同)。归纳证毕。
根据证明过程立即可得推论6:
推论6:低\(k\)位在一个周期内最多向第\(k+1\)位进位3次。
定理7:\(a[n]=O(\max(n,p,q))\)。
证明:设\(k\)是最小的满足\(n < 3 \times 2^k\)且\(p,q \le 2^{k+1}\)的正整数。那么在前\(3 \times 2^k\)个数中,低\(k+1\)位最多进位3次。因此\(a[i] < 2^{k+4}\)。
将\(k\)用\(n,p,q\)表达得\(2^k \le \max(p,q,2n/3)\),因此\(a[i] < 16\max(p,q,2n/3)\)。证毕。
定理7表明了本题所使用算法的时间复杂度为\(O(n \times \max(n,p,q))\)。(然而我不清楚出题人是否也是这么证明的(或者并没有证明直接默认了),如果有更简单的方法请务必联系我!)
PS:感谢Emil Jeřábek对以上的证明提供了巨大的帮助!
总结
在SG理论中,通过打表找出SG的规律是一类常见的题型,然而很多时候证明SG的规律是较为困难的。本文以\(a[i]=(a[i-1]\oplus a[i-2])+1\)为例详细推倒了周期性、上界等结论,这种结论实际上是很有一般性的。许多题目中SG函数均存在异或递推式,从而具有类似的周期性。然而应用SG的上界作为状态进行DP的题目则十分具有创新意义,希望以后可以出现更多的以博弈作为桥梁考察异或性质的题目。
AC代码
- #include<cstdio>
- #include<cstring>
- const long long mod = 1e18;
- int sg_1[];
- long long dp[][];
- int main()
- {
- int n, p, q;
- while (scanf("%d%d%d", &p, &q, &n) == ){
- sg_1[] = p; sg_1[] = q;
- for (int i = ; i <= n; i++)
- sg_1[i] = (sg_1[i - ] ^ sg_1[i - ]) + ;
- memset(dp[], , sizeof(dp[]));
- memset(dp[], , sizeof(dp[]));
- for (int i = ; i < p; i++)
- dp[][i] = ;
- for (int i = ; i < q; i++)
- dp[][i] = ;
- for (int i = ; i <= n; i++){
- dp[i][] = ;
- for (int j = ; j < ; j++)
- dp[i][j] = (dp[i - ][(j - ) ^ sg_1[i - ]] + dp[i - ][(j - ) ^ sg_1[i - ]]) % mod;
- }
- printf("%lld\n", dp[n][]);
- }
- }
一类SG函数递推性质的深入分析——2018ACM陕西邀请赛H题的更多相关文章
- UVa 10561 (SG函数 递推) Treblecross
如果已经有三个相邻的X,则先手已经输了. 如果有两个相邻的X或者两个X相隔一个.,那么先手一定胜. 除去上面两种情况,每个X周围两个格子不能再放X了,因为放完之后,对手下一轮再放一个就输了. 最后当“ ...
- Light OJ 1296 - Again Stone Game (博弈sg函数递推)
F - Again Stone Game Time Limit:2000MS Memory Limit:32768KB 64bit IO Format:%lld & %llu ...
- 【主席树维护mex】 【SG函数递推】 Problem H. Cups and Beans 2017.8.11
Problem H. Cups and Beans 2017.8.11 原题: There are N cups numbered 0 through N − 1. For each i(1 ≤ i ...
- 树链剖分的一种妙用与一类树链修改单点查询问题的时间复杂度优化——2018ACM陕西邀请赛J题
题目描述 有一棵树,每个结点有一个灯(初始均是关着的).每个灯能对该位置和相邻结点贡献1的亮度.现有两种操作: (1)将一条链上的灯状态翻转,开变关.关变开: (2)查询一个结点的亮度. 数据规模:\ ...
- POJ_3090 Visible Lattice Points 【欧拉函数 + 递推】
一.题目 A lattice point (x, y) in the first quadrant (x and y are integers greater than or equal to 0), ...
- UVA 11426 (欧拉函数&&递推)
题意:给你一个数N,求N以内和N的最大公约数的和 解题思路: 一开始直接想暴力做,4000000的数据量肯定超时.之后学习了一些新的操作. 题目中所要我们求的是N内gcd之和,设s[n]=s[n-1] ...
- 【基础操作】博弈论 / SG 函数详解
博弈死我了……(话说哪个小学生会玩博弈论提到的这类弱智游戏,还取石子) 先推荐两个文章链接:浅谈算法——博弈论(从零开始的博弈论) 博弈论相关知识及其应用 This article was updat ...
- [luogu]P1066 2^k进制数[数学][递推][高精度]
[luogu]P1066 2^k进制数 题目描述 设r是个2^k 进制数,并满足以下条件: (1)r至少是个2位的2^k 进制数. (2)作为2^k 进制数,除最后一位外,r的每一位严格小于它右边相邻 ...
- 7.18 NOI模拟赛 树论 线段树 树链剖分 树的直径的中心 SG函数 换根
LINK:树论 不愧是我认识的出题人 出的题就是牛掰 == 他好像不认识我 考试的时候 只会写42 还有两个subtask写挂了 拿了37 确实两个subtask合起来只有5分的好成绩 父亲能转移到自 ...
随机推荐
- Python中导入第三方声源库Acoular的逻辑解释以及Acoular的下载
[声明]欢迎转载,但请保留文章原始出处→_→ 秦学苦练:http://www.cnblogs.com/Qinstudy/ 文章来源:http://www.cnblogs.com/Qinstudy/p/ ...
- parted分区详解 查看UUID两种方式 blkid 和 ls -l /dev/disk/by-uuid
通常我们用的比较多的一般都是fdisk工具来进行分区,但是现在由于磁盘越来越廉价,而且磁盘空间越来越大:而fdisk工具他对分区是有大小限制的,它只能划分小于2T的磁盘.但是现在的磁盘空间很多都已经是 ...
- C++各个存储区
#include<iostream.h>void main(){char a[]="abc";栈 char b[]="abc";栈 char* c= ...
- Yii2访问自定义模块下的controller
之前,由于所要访问的controller都是位于根目录下的controllers目录下,就像下面这样: 此时,我们可以直接通过 localhost/basic/web/index.php?r=dao/ ...
- C++中遍历读取数组中的元素
答案来源:https://zhidao.baidu.com/question/187071815.html 对于字符数组str[N],判断方法有以下三种: 第一种:用库函数strlen 1 len = ...
- Spring Boot @Async 异步任务执行
1.任务执行和调度 Spring用TaskExecutor和TaskScheduler接口提供了异步执行和调度任务的抽象. Spring的TaskExecutor和java.util.concurre ...
- 落入绝地求生的Python神仙,实现绝地求生无后座!
叙述 绝地求生已经出来那么久了,大家应该都晓得如今的游戏情形很是差 .特别在高端局,神仙满天飞 搞得很多人类玩家很是没有游戏体验! 由于绝地求生的火爆,繁衍出许多外挂流传于各个地方.飞机上.网吧内,各 ...
- 运用jieba库分词
代码: 统计出团队中文简介中词频 import jieba txt=open("C:\\Users\\Administrator\\Desktop\\介绍.txt","r ...
- JS响应数据
页面中展示的信息都是存储在服务器中的数据,离开数据的页面就像是一块画板的作用,如何通过数据来描述一个页面,又怎么映射数据变化和页面渲染的关系. 当然,最直接的方法就是操作节点,页面加载之后获取节点,再 ...
- ubuntu 命令整合1
一.linux命令基本格式 命令名[选项] [参数] 注意: 命令名区分字母大小写 命令名必须有 选项.参数可以没有,选项一般使用减号开头二.具体Linux命令 1.who 显示登录系统中的用户的信息 ...