Combinatorial Game Theory - More examples
Catalog
Example 1
Problem Statement
We have a staircase with n stairs, and on each stair there is a certain number of stones on each stair. A player can move any positive number of stones from any stairs to the next one in a move. Stones on the ground cannot be moved. A player loses if they cannot make a move i.e. when all the stones are on the stair.
Problem Analysis
Now, solving combinatorial game theory requires a little bit of insight. Notice that our game in this case is very similar to the classical nim’s game, so perhaps the winning strategy has something to do with the xor sum of the pile sizes as well.
Now, if you still recall the three basic features of a combinatorial game, one of them is that the game is an impartial two-player game i.e. available moves and win/lose positions only depend on the current state of the game, and the only difference between the two players is that one of them starts first. Most of the time, the winning strategy involves a player being able to somehow mirror the play of another player, so that particular property of the game state is preserved, for example, the xor sum of the pile sizes.
Now we can start from the simple case:
Consider the situation where we only have one stair , with a certain number of stones on it, now the current player will of course win since he can just move everything to the floor so that the next player has no available move.
So now we can guess our winning condition: if the xor-sum is non-zero.
What if there are 2 stairs? Do the stones on the second stair matter? If you think about it, if a player moves a certain number of stones from the second stair down to the first one, then the next player can move the same number of stones from the first one to the floor, it does not affect the situation on the first stair at all. This is the mirroring of the move, which usually indicates that it does not matter for our winning strategy.
Now consider the 2nd 4th,… all the even numbered stairs, we realize that since any stones on these stairs require an even number of moves to go to the ground, so whoever moves them always leaves the other player some available move. This suggests that the stones on the even numbered stairs do not matter.
Coupled with the first case we discussed, this suggests that our winning condition has something to do with the xor sum of the odd numbered stairs pile sizes, specifically, if it is none zero, then the first player would win; and the second player wins otherwise.
Why is this so? This is the very same reasoning we discussed just now in the classical nims game: if the xor sum is non-zero, there is always an available move to make it zero; if it is zero, here we have a small twist: the current player can move the stones on the even-numbered stairs, but the next player can just move the same number of stones to the next stair, so that the xor sum of odd numbered stairs stay unchanged; if the current player chooses to move the stones on the odd numbered stairs instead, then the xor sum will change to a non-zero ones.
At the base case, we can see that xor sum of 0 makes the current player lose, this is the case we discussed initially where there is only one stair, and it has no stones on it. By the same inductive argument we used for our classical nim’s game, we have proven that our winning strategy works for all possible game states.
Code Implementation
For the implementation:
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int main(){
int n;
scanf("%d", &n);
int res = 0;
for (int i = 1; i <= n; i ++ ){
int x;
scanf("%d", &x);
if (i & 1) res ^= x;
}
if (res) puts("Yes");//First player wins
else puts("No");//First player loses
return 0;
}
Example 2
Now let us go to the second example which is slightly trickier.
Problem Statement
There are several piles of stones. A player can replace any pile of stones with 2 piles of stones of strictly smaller sizes(the new pile can be empty). A player loses if they cannot make a move i.e. when all the piles are empty.
Problem Analysis
Now, let us first introduce some useful concept here:
We define a m e x mex mex function as follows, for a set with non-negative numbers, we define its m e x mex mex value to be the smallest non-negative integer that is not in the set. Now this definition may seem a bit random to begin with, but you will see how we can make use of it. Consider any possible game states, we artificially assign each state a value, when it is 0, it represents a losing state, if it is none zero, it is a winning one. In fact this value is often known as Sprague-Grundy value, or just SG value, these two basically developed the theory we are using to solve these combinatorial puzzles
Now, this may sound really familiar to you again: we can calculate the SG value of state as the mex of the set of SG values of all states it can reach: for example, say state x goes to state a,b,c then its SG value is:
Why is this so? Again we can check by considering this: if its SG value is 0, it means it can only reaches state with none zero SG value, hence no matter how the current player moves, it always leaves the next player to the winning state, hence the current player is losing; if its SG value is non-zero, then the current player can choose a move that gets the next player to a state with SG value of 0, hence the current state is winning. This must be giving you flashbacks: yes, it is exactly the same as what we have been doing with xor sum, its just now we are talking about SG value more generally, and xor sum is just one case of all the possible forms SG value can take.
Now come back to our question, it is really convenient, since we realize that we can just twist our
m
e
x
mex
mex function a little bit: define it as the smallest non negative integer that is not in the set s, for example, consider s={3,5}:
For a pile with 8 stones,we can easily determin the SG value of all the possible states as shown above.
Now we consider all the piles, maybe you have already realized that this is just a classical nim’s game,since we can always make the xor sum zero if it is currently non zero, exactly the same argument here so im not further elaborating on this, but you can always check the previous lecture materials. Hence, we only need to compute the xor sum of all these piles to determine whether our starting state is winning or losing.
Code Implementation
#include<bits/stdc++.h>
using namespace std;
const int N=110,M=10010;
int n,m;
int f[M],s[N];
int sg(int x){
if(f[x]!=-1) return f[x];
set<int> S;
for(int i=0;i<m;i++){
int sum=s[i];
if(x>=sum) S.insert(sg(x-sum));
}
for(int i=0;;i++)
if(!S.count(i))
return f[x]=i;
}
int main(){
cin>>m;
for(int i=0;i<m;i++)cin>>s[i]>>n;
memset(f,-1,sizeof(f));
int res=0;
for(int i=0;i<n;i++){
int x;
cin>>x;
res^=sg(x);
}
if(res) puts("Yes");//First player wins
else puts("No");//First player loses
return 0;
}
Example 3
Now, let us look at a problem from this year’s USACO gold section.
Problem Statement
Bessie and Elsie are playing a game with N ( 1 ≤ N ≤ 105 ) N (1≤N≤105) N(1≤N≤105) piles of stones, where the i-th pile has a i a_i ai stones for each 1 ≤ i ≤ N ( 1 ≤ a i ≤ 106 ) 1≤i≤N (1≤a_i≤106) 1≤i≤N(1≤ai≤106). The two cows alternate turns, with Bessie going first.
First, Bessie chooses some positive integer s 1 s_1 s1 and removes s 1 s_1 s1 stones from some pile with at least s 1 s_1 s1 stones. Then Elsie chooses some positive integer s 2 s_2 s2 such that s 1 s_1 s1 divides s 2 s_2 s2 and removes s 2 s_2 s2 stones from some pile with at least s 2 s_2 s2 stones. Then Bessie chooses some positive integer s 3 s_3 s3 such that s 2 s_2 s2 divides s 3 s_3 s3 and removes s 3 s_3 s3 stones from some pile with at least s 3 s_3 s3 stones and so on. In general, s i s_i si, the number of stones removed on turn i i i, must divide s i + 1 s_{i+1} si+1. The first cow who is unable to remove stones on her turn loses.
Compute the number of ways Bessie can remove stones on her first turn in order to guarantee a win (meaning that there exists a strategy such that Bessie wins regardless of what choices Elsie makes). Two ways of removing stones are considered to be different if they remove a different number of stones or they remove stones from different piles.
Problem Analysis
Now this problem is again slightly different from the ones we encounter before, instead of just simply determine whether our starting state is winning or losing, we are finding how many ways are there for the first player to win. Of course, winning is really easy for the first player in most cases, if there exists a pile that has more stones than the rest, she can just take the whole of it, then the other player would have no possible moves. Now this actually gives us some insights: what if there are 2 piles that have the same number of piles, more than the rest? In this case, the next player can always just mirror the first player’s move, which indicates that the first player is losing.
Another useful insight would be that instead of trying to make each move taking stones of a size being the multiple of the previous one, we can consider it as dividing all of the pile sizes by some positive integer and subtracting one from some pile with a positive size.
Now of course for a combinatorial game theory question, after observation, we will have to guess the winning condition. In this case it is not hard to guess since we have already figure out in what cases the following player can always mirror the move of the first player: when the pile size is even. Hence, we claim that a state in the game is losing for the first player if and only if for each x≥1, the number of piles of size x is even. In this case, the second player can win by simply mirroring the moves of the first player. In all other states, let x the maximum pile size such that the number of piles of size exactly x is odd. Then the first player wins if she removes that pile, because she will leave an all-even case for the following player. Now, by a similar inductive argument we used for any nim’s game, since we have proven in the base case this is true: the base case being there are 2 piles of same size, the first player loses, and if there are 1 pile of any size, the current player wins.
So the discussion of the winning strategy is pretty simple for this question, even easier than the last one since we do not actually need to think of a way to represent the sg function, thanks to the fact that the game itself does not involve a lot of rules. The only problem left would be how to count the number of ways that our first player bessie can win.
Now suppose that Bessie removes x x x stones from some pile on her first turn. Then we need to count the number of integers among the sequence S x = [ ⌊ a 1 x ⌋ , ⌊ a 2 x ⌋ , ⌊ a 3 x ⌋ , … , ⌊ a n x ⌋ ] S_x=[⌊\frac{a_1}{x}⌋,⌊\frac{a_2}{x}⌋,⌊\frac{a_3}{x}⌋,…,⌊\frac{a_n}{x}⌋] Sx=[⌊xa1⌋,⌊xa2⌋,⌊xa3⌋,…,⌊xan⌋] (mind the floor function) such that when decreased by one, every positive integer in the sequence occurs an even number of times (ignoring zero). So Bessie wins if she picks t > 0 t>0 t>0 such that:
- t t t occurs an odd number of times in S x S_x Sx
- If t > 1 t>1 t>1, t − 1 t−1 t−1 occurs an odd number of times in S x S_x Sx
- No other positive integer occurs an odd number of times in S x S_x Sx
For each x x x and t t t, the number of occurrences of t t t in S x S_x Sx is equal to the number of integers in the input sequence that are in the range [ x t , x ( t + 1 ) − 1 ] [xt,x(t+1)−1] [xt,x(t+1)−1]. For a fixed x x x, we can compute this quantity for all possilbe t t t in linear time using prefix sums.
We enumerate all possible x, then we check which numbers fulfill all the requirements in S x S_x Sx and we update the answer accordingly.
Code Implementation
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N=1e5+5,M=1e6+5;
int n,pre[M],cnt[M];
int main(){
scanf("%d",&n);
int mv=0;
for(int i=1;i<=n;i++){
int x;
scanf("%d",&x);
pre[x]++;
mv=max(mv,x);//finding the max pile size
}
for(int i=1;i<=mv;i++)pre[i]+=pre[i-1];//prefix sum
LL ans=0;
for(int k=1;k<=mv;k++){
int odd=0,fo=0;//odd is the counter of odd occurences,fo is the first occurence
for(int mul=1;mul*k<=mv;mul++){
cnt[mul]=pre[min(mul*k+k-1,mv)]-pre[mul*k-1];
if(cnt[mul]%2==1)//odd number of occurences
{
odd++;
if(odd==1)fo=mul;
else if(fo!=mul-1)odd++;//does not fulfill second requirement, set odd to 3 to exit the loop
if(odd>=3)break;
}
}
if(odd==2)ans+=cnt[fo+1];
else if(odd==1&&fo==1)ans+=cnt[1];//special case
}
printf("%lld",ans);
}