问题一:有n堆物品各若干,两人轮流从某一对取任意多物品,规定每次至少取一个,多者不限,最后取光者胜利
用(a,b,c)表示局势,若a xor b xor c==0,那么(a,b,c)为奇异局势,面对奇异局势必败。若(a,b,c)为非奇异局势,则只要将c变为a xor b即变成奇异局势
若a1 xor a2 xor a3 ... == 0 ,则称(a1,a2,a3......)为T态(利他态),反之称S态(利己态)
定理1:S->T,只要将某数 - x 即可
证明:设 c = a1 xor a2 xor a3 ... xor an > 0,且c的第最高位(二进制)1为p位,那么必存在at,其第p位也是1,令x=at xor c < at
那么把原式的at 换成 x ,原式 = a1 xor a2 xor a3 ... x xor ... an = a1 xor x2 ... xor an xor c = 0; S->T转换完成
定理2:T->S,任何操作
证明:反证法:若 c = a1 xor a2 xor a3 ... xor an = 0;
且 c' = a1 xor a2 xor a3... at' xor ... an = 0;
则 c xor c' = 0; 但是c xor c' = at xor at' !=0 矛盾
定理3:S必赢
证明:先手S->T(定理1),后手必有T->S(定理2),所以先手必将局势控制在S态
定理4:T必败
证明:定理(3)
问题二:若某堆数量为1,则称孤单堆,若大于1,则为充裕堆。每个人 轮流在一堆中取若干根,最后取完为负
定理:显然不存在T1,充裕堆不可能一次消去两个
定理5:S0必败,T0必胜
证明:S0即是奇数个孤单堆,每次只能取一根,每次第奇数根都由自己取,第偶数根都由对方取,所以必败
定理6:S1态必胜
证明:先手S1->S0,后手必败(定理5)
定理7:S2≠>T0
证明:充裕堆不可以一次消去两个
定理8:S2->T2
证明:S->T(定理1),不可能转换成T0(定理7),所以转换成T2态
定理9:只有T2->S2/S1
证明:T->S(定理2),不可能是S0
定理10:S2 必胜
证明:先手S2->T2(定理8),必有T2->S2/S1(定理9),所以先手可以将状态控制在S2,或者变成S1(必胜态)
定理11:T2必输
证明:定理10
总结:第一题(最后取则胜)策略,先手将S1变为T0,那么每次轮到先手都是S0(奇数孤单堆),则必取最后一堆:
S2->T2->S2->T2.......T2->S1-------------->T0->S0->T0->S0->T0....->S0->T0(全0)
第二天(最后取则败)策略,先手将S1变为S0,那么每次轮到先手都是T0(偶数孤单堆),则对方必取最后一堆
S2->T2->S2->T2.......T2->S1-------------->S0->T0->S0->T0....->S0->T0(全0)
所以要取胜就要取得S1,就要不断把T2留给对方,如果自己是T2,则必败
例题hdu1907 (问题二模板)
#include<bits/stdc++.h> using namespace std; int main(){ int t,n,a[10000]; scanf("%d",&t); while(t--){ scanf("%d",&n); int tmp=0; for(int i=1;i<=n;i++){ scanf("%d",&a[i]); if(a[i]>1) tmp++; } if(tmp==0){ if(n%2==1) //S0态必败 puts("Brother"); else puts("John"); continue; } else if(tmp==1){//S1->S0,对方必败 puts("John"); continue; } else {//S2必胜,T2必败 int tot=0; for(int i=1;i<=n;i++) tot^=a[i]; if(tot!=0) puts("John"); else puts("Brother"); } } }
hdu2509 问题一,但是要证明在该题情况下T2必会变成S2/S1,剩下的状况S1,S0,T0则不受影响
证明:T状态必变成S,所以尽管对T2中的某一堆进行改变后,可能会产生新的两堆,但是局势仍是S
at' = at1'+at2' 且at' != at, 则at != at1' xor at2' ,
#include<bits/stdc++.h> using namespace std; int main(){ int t,n,a[1000]; while(scanf("%d",&n)==1){ int tmp=0; for(int i=1;i<=n;i++){ scanf("%d",&a[i]); if(a[i]>1) tmp++; } if(tmp==0){ if(n%2==0)//S0态必胜 puts("Yes"); else puts("No"); continue; } else if(tmp==1){//S1态必胜 puts("Yes"); continue; } else { int tot=0; for(int i=1;i<=n;i++) tot^=a[i]; if(tot==0) puts("No");//T2必败 else puts("Yes"); } } return 0; }