题目描述
定义一种有根二叉树(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)(ile n))的SG值。
转化为动态规划问题
如果原问题问谁赢那么就已经做完了。然而现在问的是第一个人第一步有几种能赢的方案,也就是说有几种拿走子树的方案使得之后SG为0。
考虑动态规划。设(dp[i][j])表示在树(T(i))上玩游戏,先手拿走一棵子树后SG值变为(j-1)的方案数(这里(j-1)是为了后续方便),(dp[i][0])恒定义为1。
初始化:对任意(1le j < p),(dp[1][j]=1);对任意(1le 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 imes 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 imes 2^{k-2}]_k)再次被取反从而不变了,之后(a[x+1+3 imes 2^{k-2}]_k)也不变……这样一来,(a[x])和(a[x+3 imes 2^{k-2}])第(k)位必然不同。进一步地,设(x mod 3=i),则对于所有(j),(a[3j+i])和(a[3j+i+3 imes 2^{k-2}])第(k)位必然不同。
对于基础里的两种情况,无论是有一次进位,还是有3次进位但两次位置模3余数相同(从而抵消了),均可以保证(a[3j+i]_k eq a[3j+i+3 imes 2^{k-2}]_k)。从而(a[i] mod 2^k)的循环节为(3 imes 2^{k-1})。
接下来再得到第(k)位向(k+1)位一个循环内的进位情况。由于对于位置(x)和(x+3 imes 2^{k-2})低(k-1)位向第(k)位分别有一次进位,而(a[x]_k)和(a[x+3 imes 2^{k-2}]_k)又不同,故必有且仅有一次使得低(k)位向第(k+1)位进位。因此在一个周期(3 imes 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 imes 2^k)且(p,q le 2^{k+1})的正整数。那么在前(3 imes 2^k)个数中,低(k+1)位最多进位3次。因此(a[i] < 2^{k+4})。
将(k)用(n,p,q)表达得(2^k le max(p,q,2n/3)),因此(a[i] < 16max(p,q,2n/3))。证毕。
定理7表明了本题所使用算法的时间复杂度为(O(n imes 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代码
1 #include<cstdio> 2 #include<cstring> 3 const long long mod = 1e18; 4 int sg_1[2001]; 5 long long dp[2001][8192]; 6 int main() 7 { 8 int n, p, q; 9 while (scanf("%d%d%d", &p, &q, &n) == 3){ 10 sg_1[1] = p; sg_1[2] = q; 11 for (int i = 3; i <= n; i++) 12 sg_1[i] = (sg_1[i - 2] ^ sg_1[i - 1]) + 1; 13 memset(dp[1], 0, sizeof(dp[1])); 14 memset(dp[2], 0, sizeof(dp[2])); 15 for (int i = 0; i < p; i++) 16 dp[1][i] = 1; 17 for (int i = 0; i < q; i++) 18 dp[2][i] = 1; 19 for (int i = 3; i <= n; i++){ 20 dp[i][0] = 1; 21 for (int j = 1; j < 8192; j++) 22 dp[i][j] = (dp[i - 1][(j - 1) ^ sg_1[i - 2]] + dp[i - 2][(j - 1) ^ sg_1[i - 1]]) % mod; 23 } 24 printf("%lld ", dp[n][1]); 25 } 26 }