「EZEC」 Round 6 周年欢乐赛 象棋题解
P0:题外话
这题的原身是不存在博弈的,仅仅是给一个开局,问是否能够操作使得最后只剩两个棋子。但是我觉得博弈可做,于是在群上发了问,结果被神仙 @Forever_Pursuit 搞出来了。
然后还是第一次有人帮我做数据,验题和推广,之前出的题基本上都是我自己出自己验的,果然有神仙在团内就是不一样/cy
P1:基础观察
首先需要证明两个结论
- 如果当前局面下红方可以操作,那么黑方必然也可以,反之亦然。
证明:一个可操作的局面仅有以下几种可能。
() 100 ()
() 001 ()
() 011 ()
() 110 ()
其中 () 表示任意数量的棋子。
可以发现,在这四种状态下红黑双方均能行动,故命题得证。
由此引出下一个结论:
- 如果当前局面可操作,那么选择操作严格不劣于不操作。
证明:
由于每个操作都只会改变减少对方棋子的数量,而若一方能操作另一方也能操作,那么可以得出若一方不操作,让另一方操作只会减少自己的棋子数量,且不会影响对方之后的操作。故命题得证
P2:题解
对于
对于
暴力 dfs 寻找每一种可能。假设在某个状态下由红方行动,而他的操作会使黑方必败,则红方有必胜策略。若有平局状态,则红方可以选择平局 ,否则当前状态红方必败。(黑方同理)
暴力复杂度
对于
思路跟dfs一样,只不过用二进制记录状态,
接下来开始讲正解思路
由于之前证明的结论,我们可以得出,若红方初始棋子数量大于黑方,那么红方必胜。
同理,若红方初始棋子数量小于黑方棋子-1,那么红方必败。
现在仅有两种情况需要讨论:
- 红棋数量=黑棋数量
- 红棋数量=黑棋数量-1
首先讨论第一种情况:
通过基础观察,我们可以发现若红棋数量=黑棋数量,红棋只可能胜利或者平局。红棋胜利的可能仅有一个:当某一步红棋走完后,黑棋无法再次行动。
考虑什么时候会出现这种情况:
定义 () 集合为一组数字出现任意次数,例如 (101) 可能为 101101,也可能是空集。
对于一个不能动的情况,由于红棋数量大于黑棋,故最后局面必然是 (10)1 这一种情况。
发现仅有两种情况能够转移得到这种情况:
(10)1100(01)(10)0011(01)
由于这两种状态仅仅是 01 的位置反了过来,故我们可以认为他们等价,下面仅对第一种情况进行讨论。
继续考虑,发现仅有以下几种情况能得到上述情况:
(10)11100(01)(10)11001(01)(10)10110(01)
由于初始棋子数量相等,而括号内棋子数量也平衡。根据初始证明,可以得出在出现这种状况时必然轮到黑方操作。
可以发现,对于上述每一种状态,黑棋必然能够使用最佳策略避开必输局面。故如果在这种情况下轮到黑方操作,双方必然平手。
对于这种情况,红方仅有一种赢的可能:当 (10)1100(01) 或 (10)0011(01) 为初始局面。
结论:如果初始局面可以通过一次转移为不可操作局面,则红方必胜,否则必然平手。
接下来讨论第二种情况:
若红棋数量=黑棋数量-1,如果对上面的证明进行分析,那么我们可以发现此时红方的处境等价于黑方的处境。
结论:若初始局面为可操作局面,则必然平局,否则红方必败。
正解证明完成,开始讨论做法
对于
由于出题人不确定有没有
对于
发现若有一个位置能够使得变化和局面可操作,则继续判断无意义。
故对于红棋=黑棋时仍然暴力枚举,但是枚举时随机跳位置检索是否不可操作。
复杂度不太会证,但实际效果优秀,可以过
对于
上面已经讲过,仅存在 (10)0011(01) 和 (10)1100(01) 两种可能。故我们可以判断数组是否为这种情况,具体写法将在代码中给出。
复杂度
const int MAXN = 1e7+5;
const int MAXM = 1e5+5;
const int mod = 1e9+7;
int dx[4] = {1,0,-1,0}, dy[4] = {0,1,0,-1};
int n,m,t,pos[MAXN],k,a,b,c,red,black;
inline void case1(){
int pivot = 0,ab = 0;
For(i,1,n-1)
if(pos[i]==pos[i+1]) {
if (pivot && pos[i]==1) {puts("TIE");return;}
else if (pos[i]==1) pivot = i;
if (pos[i]==0) ab++;
}
if (!pivot) {puts("TIE");return;}
if ((pivot == 3 && pos[1] == pos[2]) || (pivot == n-3 && pos[n-1] == pos[n])) {puts("WIN");return;}
if (ab == 2 && ((pivot > 3 && !pos[pivot-1] && !pos[pivot-2] && !pos[pivot-3]) ||
(pivot<=n-4 && !pos[pivot+2] &&!pos[pivot+3] && !pos[pivot+4]))) {puts("WIN");return;}
puts("TIE");
}
inline void case2(){
For(i,1,n-1) if (pos[i]==pos[i+1]) {puts("TIE");return;}
puts("LOSE");
}
signed main(){
t = read();pos[0] = -1;
while(t--){
n = read();pos[n+1] = pos[n+2] = pos[n+3] = -1;
red = 0;
For(i,1,n) pos[i] = (get()=='1'),red+=(pos[i]==1);
black = n-red;
if (red>black) puts("WIN");
else if (red<=black-2) puts("LOSE");
else if (red==black) case1();
else case2();
}
}