题目链接
http://acm.hdu.edu.cn/showproblem.php?pid=1848
这个题目代码不是很复杂,但那个等价类,(SG函数)没怎么理解,
题目难,不过代码不怎么复杂,在网上找了一些解释,
博弈题,一开始好难理解。还是用到了那个定理
对于nim游戏的某个位置(x1,x2,x3),当且仅当它各部分的nim-sum等于0时(即x1⊕x2⊕x3=0),则当前位于必败点
当时我在网络上找教程,介绍了好多,我也花了很长时间才懂的,待会我会给那个大牛的解题报告的链接,这里我就说说我的理解
这道题用到了上面那个定理,有3堆,每次只能从每堆那里移除斐波那契数的石子。由于每堆有不同的取法,所以把可能出现同种情况的数字归为一类。称为等价类。每一个类又对应一个数叫做等价类数
等价类数的算法(0其实就是必败的点,对于一堆来说)
E[0]=0;
等价类数 E[i] 的算法:
从i个中取走 fib[1],fib[2],...,fib[j]<=i 个后剩下i-fib[1], i-fib[2],..., i-fib[j]个
他们的等价类数中没有出现的最小数就是i的等价类数
例如 i=1,
取走fib[1]=1个 i-fib[1]=0,0的等价类数是0,没有出现的最小数就是1
E[1]=1;
例如 i=2,
取走fib[1]=1个 i-fib[1]=1,取走fib[2]=2个 i-fib[1]=0,
1和0的等价类数是1,0,没有出现的最小数就是2
E[2]=2;
例如 i=3
取走fib[1]=1个 i-fib[1]=2,取走fib[2]=2个 i-fib[1]=1,取走fib[3]=3个 i-fib[1]=0,
2,1和0的等价类数是2,1,0,没有出现的最小数就是3
E[3]=3;
例如 i=4
取走fib[1,2,3]=1,2,3个 剩下3,2,1,没有出现的最小数就是0
E[4]=0; 4是必败点
例如 i=5
取走fib[1,2,3,4]=1,2,3,5个 剩下4,3,2,0,等价类数是 0,3,2,0没有出现的最小数就是1
E[5]=1;
这样就得到了一个等价类数数组。接着就运用那个定理,如果一开始就出现必败点,即(e[n] ^ e[m] ^ e[p]) == 0。那么按照最优走法则必输。其它情况必赢。
详细可看详解。
http://hi.baidu.com/king___haha/blog/item/9addc65ae96948272934f029.html
代码(code)
#include<stdio.h>
#include<string.h>
int main(void)
{
int i,j,k,n,m,p;
int a[1005],b[20],c[16];//才数组为什么只要开到16呢?
b[1]=1; b[2]=2;
for(i=3;i<=16;i++)
b[i]=b[i-1]+b[i-2];
a[0]=0; a[1]=1;
for(i=2;i<=1000;i++)
{
a[i]=i;
memset(c,0,sizeof(c));
for(j=1;b[j]<=i;j++)
{
c[a[i-b[j]]]=1;
}
for(j=0;j<=15;j++)//为什么只要15看可以了呢?
{
if(c[j]==0)
{
a[i]=j;
break;
}
}
}
while(scanf("%d%d%d",&n,&m,&p)==3&&(n+m+p))
{
if(a[n]^a[m]^a[p])
printf("Fibo\n");
else
printf("Nacci\n");
}
return 0;
}
hdu1848 Fibonacci again and again 3堆
解题报告
fib[1..]={1,2,3,5,8,13,21,...};是菲波那契数列,fib[]<=1000
每次只能取 fib[i]个
1. 如果只有1堆m个,m是某个fib[i],m是必胜点,m=1,2,3,5,8,13,21,...是必胜点
易知0,4 是必败点.如果从m个中取走 k个(k=fib[i]) ,m-k是必败点
则 m是必胜点.
6-2=7-3=9-5=4是必败点, 6,7,9 是 必胜点
m=10,m-1=9,m-2=8,m-3=7,m-5=5,m-8=2
从m=10中所有取法后都是必胜点 ==> m=10是必败点
这样当只有1堆时我们可以求所有必败点,用 w[1001]表示,w[i]=0表示i是必败点
w[i]=1表示i是必胜点,
w[0]=0;w[1]=w[2]=w[3]=1;
for(i=4;i<=1000;i++)
{
for(j=1;fib[j]<=i;j++) if(w[i-fib[j]==0){w[i]=1;break;}
//取所有的Fibonacci数都胜,则该点必败
if(fib[j]>i)w[i]=0;
}
只有1堆时所有必败点:
0 4 10 14 20 24 30 36 40 46 50 56 60 66 72 76 82 86 92 96 102 108 112 118 122 128
132 138 150 160 169 176 186 192 196 202 206 212 218 222 228 232 238 242 248 254
260 264 270 274 280 284 290 296 300 306 310 316 322 326 332 338 342 348 352 358
364 368 374 378 384 388 394 400 406 410 416 420 426 430 436 442 446 452 456 462
468 472 478 484 488 494 498 504 510 514 520 524 530 534 540 552 556 562 566 572
576 582 588 592 598 602 608 618 635 644 662 672 688 694 698 704 708 714 723 730
734 740 750 754 766 772 776 782 791 798 808 814 818 824 830 834 840 844 850 854
860 866 872 876 882 886 892 896 902 908 912 918 922 928 934 938 944 950 954 960
964 970 976 980 986 990 996 1000
2. 有2堆m个和 n个
如果 m和 n都是 (1堆的)必败点,则2堆 (m,n) 也是必败点
一般 有 必败+必败=必败, 这里+不是把2队合为1堆
必败+必胜=必胜
必胜+必胜=?不一定
例如 2堆 (4,10) 是必败
(4, 5) 是必胜
易知 ( m,m )必败
定义: 如果 (m,n) 必败,则称 m和n等价
等价是相等的推广:m=n, m和n等价
只有1堆时所有必败点都和0等价,我们说只有1堆时所有必败点是第0类的或他的等价类数是0;
2堆 (1,5) 是必败的,5和1等价,和1等价的m叫做他的等价类数是1
2堆时的情况用 w2[m1][m2] 表示,w2[m1][m2]=0表示 (m1,m2)必败,
w2[m1][m2]=1表示 (m1,m2)必胜
用前面方法可算出等价类数是1的数,例如 1,5,11的等价类数是1
可以进行等价代换例如 3堆 1,5,7 可代换为 1,1,7,是必胜的
3. 结论(不证明了) 如果算出 m,n,p的等价类数是,E[m],E[n],E[p]
令 s=E[m]^E[n]^E[p]
则 s=0 是必败点
等价类数的算法
E[0]=0;
等价类数 E[i] 的算法:从i个中取走 fib[1],fib[2],...,fib[j]<=i 个后剩下
i-fib[1], i-fib[2],..., i-fib[j]个
他们的等价类数中没有出现的最小数就是i的等价类数
例如 i=1,取走fib[1]=1个 i-fib[1]=0,0的等价类数是0,没有出现的最小数就是1
E[1]=1;
例如 i=2,取走fib[1]=1个 i-fib[1]=1,取走fib[2]=2个 i-fib[1]=0,
1和0的等价类数是1,0,没有出现的最小数就是2
E[2]=2;
例如 i=3,取走fib[1]=1个 i-fib[1]=2,取走fib[2]=2个 i-fib[1]=1,取走fib[3]=3个 i-fib[1]=0,
2,1和0的等价类数是2,1,0,没有出现的最小数就是3
E[3]=3;
例如 i=4,取走fib[1,2,3]=1,2,3个 剩下3,2,1,没有出现的最小数就是0
E[4]=0; 4是必败点
例如 i=5,取走fib[1,2,3,4]=1,2,3,5个 剩下4,3,2,0,等价类数是 0,3,2,0没有出现的最小数就是1
E[5]=1;
//计算等价类数
E[0]=0;E[1]=1;
for(i=2;i<=1000;i++)
{ //首先假设 i<=1000时 等价类数<=15
for(j=0;j<=15;j++)h[j]=0;// h[j]==0, j没有出现,h[j]=1,j出现
for(j=1;fib[j]<=i;j++)
{
h[ E[i-fib[j]] ]=1;
}
for(j=0;j<=15;j++)if(h[j]==0){E[i]=j;break;}//h[j]=0,j没有出现
}
因此计算步骤是:
1.计算菲波那契数列fib[i],计算到 fib[i]>1000为止
2.计算等价类数 E[i]
3. 同上面的 3