Thanos Nim
n
n
n 堆石头,是个偶数,第
i
i
i 堆有
a
i
a_i
ai 个石头。 两人轮流取,每次必须选择
n
/
2
n/2
n/2 堆,每堆自行选择拿走任意正整数个石头。谁不能操作谁就输。 问谁必胜。
2
≤
n
≤
50
2\le n\le 50
2≤n≤50
1
≤
a
i
≤
50
1\le a_i\le 50
1≤ai≤50
题意
我感觉这
2000
2000
2000 的题比
2300
2300
2300 的还难想… 首先手搓一下
n
=
2
n=2
n=2,容易得到如果
a
1
=
a
2
a_1=a_2
a1=a2 先手输,否则先手胜。难道是对称性构造?还是和
N
i
m
Nim
Nim 有关系?
但是手搓一下
n
=
4
n=4
n=4,发现和
N
i
m
Nim
Nim 没有什么关系。某一堆石头如果是数量最大的,那么它多加再多的石头,也不会影响先手的胜负。在
n
>
=
4
n>=4
n>=4 时对称性构造也很难证明到底是否先手必胜。
我们举一些特例来慢慢渐入。首先,先手必须要选
n
/
2
n/2
n/2 堆。容易想到,如果先手拿完了一堆或者更多堆,后手直接拿光另外
n
/
2
n/2
n/2 堆,先手就输了。所以先手不能拿完任何一堆。 也就是说,至少有
n
/
2
n/2
n/2 堆,满足
a
i
>
1
a_i>1
ai>1。 在上面的基础上,如果小于
n
n
n 堆满足
a
i
>
1
a_i>1
ai>1,那么后手肯定会至少拿完一堆,后手也就输了。
于是我们发现,设
t
t
t 表示有多少个
i
i
i 满足
a
i
=
1
a_i=1
ai=1
t
∈
[
n
/
2
+
1
,
n
]
t\in[n/2+1,n]
t∈[n/2+1,n],先手输。
t
∈
(
0
,
n
/
2
]
t\in(0,n/2]
t∈(0,n/2],后手输。
t
=
0
t=0
t=0,我们还不大清楚。
接下来就比较难想了。我们要让结论有一般性,满足我们的做题需求。 设
t
t
t 表示有多少个
i
i
i 满足
a
i
=
min
j
∈
[
1
,
n
]
{
a
j
}
a_i=\min_{j\in [1,n]}\{a_j\}
ai=minj∈[1,n]{aj} 简单来说,就是有多少堆,满足这堆石头的数量是最少的。 如果
t
>
n
/
2
t>n/2
t>n/2,那肯定玩家会选到最少石头的堆,于是最少石头的堆的数量更新了,很容易得到为
1
≤
t
≤
n
/
2
1\le t\le n/2
1≤t≤n/2 如果
t
≤
n
/
2
t\le n/2
t≤n/2,玩家可以通过选择,选不是最少石头的堆,来让
t
>
n
/
2
t>n/2
t>n/2。 于是我们发现,经过一轮,
t
>
n
/
2
t>n/2
t>n/2 与
t
≤
n
/
2
t\le n/2
t≤n/2 来回互换。
我们查看一下边界条件。无法操作,肯定是
0
0
0 的数量
>
n
/
2
>n/2
>n/2 了。 所以我们只要查看,一开始的
t
t
t 是 哪个状态,最终就是
A
l
i
c
e
Alice
Alice 的状态。
代码
时间复杂度:
O
(
n
)
O(n)
O(n)
#include<bits/stdc++.h>#defineIOSios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;typedeflonglong ll;voidshow(){std::cerr << endl;}template<typename T,typename... Args>voidshow(T x,Args... args){std::cerr <<"[ "<< x <<" ] , ";show(args...);}constint MAX =505;constint MOD =998244353;constint INF =0x3f3f3f3f;const ll LINF =0x3f3f3f3f3f3f3f3f;constdouble EPS =1e-7;intmain(){int n;cin >> n;int mn = INF;int cnt =0;for(int i =1;i <= n;++i){int t;cin >> t;if(t == mn)cnt++;elseif(t < mn){
mn = t;
cnt =1;}}if(cnt <= n /2)puts("Alice");elseputs("Bob");return0;}
F:Game of Stones | CF 768E | 2100
题意
Game of Stones 有
n
n
n 堆,第
i
i
i 堆有
a
i
a_i
ai 个石头。两人轮流操作。 一次操作,玩家选择一堆,从中拿
x
x
x 个石头。这之后,这堆石头就不能一次性拿
x
x
x 个石头了。 谁不能操作就输,求必胜情况。
1
≤
n
≤
1
0
6
1\le n\le 10^6
1≤n≤106
1
≤
a
i
≤
60
1\le a_i\le 60
1≤ai≤60
题意
注意到,
a
i
a_i
ai 很小。
N
i
m
Nim
Nim 石头堆直接异或一下他们的
s
g
sg
sg 值就行了。
s
g
sg
sg 值直接暴力打表就行了。 怎么打表呢?我们记
s
g
[
i
]
[
j
]
sg[i][j]
sg[i][j] 表示现在这堆里有
i
i
i 个石头,下一次拿石头必须数量要
>
j
>j
>j 个。 因为拿石头数量不能重复,我们最后相当于是升序拿石头。所以可以这样做。
代码
时间复杂度:
O
(
n
+
6
0
2
)
O(n+60^2)
O(n+602)
#include<bits/stdc++.h>#defineIOSios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;typedeflonglong ll;voidshow(){std::cerr << endl;}template<typename T,typename... Args>voidshow(T x,Args... args){std::cerr <<"[ "<< x <<" ] , ";show(args...);}constint MAX =105;constint MOD =998244353;constint INF =0x3f3f3f3f;const ll LINF =0x3f3f3f3f3f3f3f3f;constdouble EPS =1e-7;int sg[MAX][MAX];intSG(int x,int l){if(sg[x][l]!=-1)return sg[x][l];if(x <= l)return0;
map<int,int>M;for(int i = l+1;i <= x;++i){
M[SG(x-i,i)]=1;}for(int i =0;;++i){if(!M[i]){return sg[x][l]= i;}}}intmain(){for(int i =0;i <=60;++i)for(int j =0;j <=60;++j)
sg[i][j]=-1;int n;scanf("%d",&n);int ans =0;for(int i =1;i <= n;++i){int t;scanf("%d",&t);
ans ^=SG(t,0);}puts(!ans?"YES":"NO");return0;}
G:Industrial Nim | CF15C | 2000
题意
Industrial Nim 有
n
n
n 个采石场,第
i
i
i 个采石场有
m
i
m_i
mi 堆石头 ,其中的第
i
i
i 个采石场的第
j
j
j 堆石头的个数为
a
i
+
j
−
1
a_i+j-1
ai+j−1 玩
N
i
m
Nim
Nim ,求问胜负情况。
1
≤
n
≤
1
0
5
1\le n\le 10^5
1≤n≤105
1
≤
a
i
,
m
i
≤
1
0
16
1\le a_i,m_i\le 10^{16}
1≤ai,mi≤1016
思路
正常的
N
i
m
Nim
Nim 游戏,每一个采石场相当于
m
i
m_i
mi 堆石头堆,它的
s
g
sg
sg 值就是这
m
i
m_i
mi 堆石头的异或值 所以容易得到,第
i
i
i 个采石场的
s
g
sg
sg 值就是
a
i
⊕
(
a
i
+
1
)
⊕
⋯
⊕
(
a
i
+
m
i
−
1
)
a_i\oplus(a_i+1)\oplus\cdots\oplus(a_i+m_i-1)
ai⊕(ai+1)⊕⋯⊕(ai+mi−1) 由异或值的性质,我们定义
f
(
i
)
=
1
⊕
2
⊕
⋯
⊕
i
f(i)=1\oplus2\oplus\cdots \oplus i
f(i)=1⊕2⊕⋯⊕i 答案就是
f
(
a
i
+
m
i
−
1
)
⊕
f
(
a
i
−
1
)
f(a_i+m_i-1)\oplus f(a_i-1)
f(ai+mi−1)⊕f(ai−1)
接下来,我们需要知道异或前缀和
f
(
x
)
f(x)
f(x) 怎么快速求。 容易想到,我们需要写出
x
x
x 的二进制表示,把它表示成多段的异或值。比如:
1111
⊕
−
−
0001
⋮
1000
−
−
1001
⋮
1100
−
−
1101
1110
−
−
1111
\begin{matrix} 1111\\ \oplus--\\ 0001\\ \vdots\\ 1000\\ --\\ 1001\\ \vdots\\ 1100\\ --\\ 1101\\ 1110\\ --\\ 1111 \end{matrix}
1111⊕−−0001⋮1000−−1001⋮1100−−11011110−−1111 容易发现,
f
(
2
i
)
=
2
i
f(2^i)=2^i
f(2i)=2i,其中
i
≥
2
i\ge 2
i≥2
f
(
2
1
)
=
3
f(2^1)=3
f(21)=3
容易想到,第一段的答案就是
f
(
2
i
)
f(2^i)
f(2i) 第二段,行数是
2
i
2^i
2i,如果
i
>
1
i>1
i>1,行数是偶数,那么前面的那些
1
1
1 异或完后就是
0
0
0 了。 如果
i
=
0
i=0
i=0,行数为
1
1
1,那么答案最终多异或一个
x
x
x
代码
时间复杂度:
O
(
n
log
(
a
i
+
m
i
)
)
O(n\log (a_i+m_i))
O(nlog(ai+mi)),我经过优化之后变成了
O
(
n
)
O(n)
O(n)
#include<bits/stdc++.h>#defineIOSios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;typedeflonglong ll;voidshow(){std::cerr << endl;}template<typename T,typename... Args>voidshow(T x,Args... args){std::cerr <<"[ "<< x <<" ] , ";show(args...);}constint MAX =505;constint MOD =998244353;constint INF =0x3f3f3f3f;const ll LINF =0x3f3f3f3f3f3f3f3f;constdouble EPS =1e-7;
ll getXor(ll x){// 推导了一些,变得更加简洁了。
ll ans = x;if(x &2)ans ^=1;if(x &1)ans ^= x ^1;return ans;}intmain(){int n;scanf("%d",&n);
ll ans =0;for(int i =1;i <= n;++i){
ll x,m;scanf("%lld%lld",&x,&m);
ans ^=getXor(x-1);
ans ^=getXor(x+m-1);}puts(ans?"tolik":"bolik");return0;}
H:The Game Of Parity | CF 549C | 2200
题意
The Game Of Parity | CF 549C | 2200 有
n
n
n 个盒子,第
i
i
i 个盒子有
a
i
a_i
ai 个球。 两人轮流操作。玩家选择一个盒子,然后把这个盒子与里面的球都丢掉。 最后只剩下
k
k
k 个盒子之后,看球的数量的奇偶性。奇则先手赢,否则先手输。 问胜负情况。
1
≤
k
≤
n
≤
2
⋅
1
0
5
1\le k\le n\le 2\cdot10^5
1≤k≤n≤2⋅105
#include<bits/stdc++.h>#defineIOSios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;typedeflonglong ll;voidshow(){std::cerr << endl;}template<typename T,typename... Args>voidshow(T x,Args... args){std::cerr <<"[ "<< x <<" ] , ";show(args...);}constint MAX =105;constint MOD =998244353;constint INF =0x3f3f3f3f;const ll LINF =0x3f3f3f3f3f3f3f3f;constdouble EPS =1e-7;voidout(int mex){puts(mex?"Stannis":"Daenerys");}intmain(){int n,k;scanf("%d%d",&n,&k);int shu[2]={};for(int i =1;i <= n;++i){int t;scanf("%d",&t);
shu[t%2]++;}int S =(n - k +1)/2;int D =(n - k)/2;int STEP =(n - k);if(STEP ==0){out(shu[1]%2);return0;}if(STEP &1){if(D >= shu[1]){out(0);return0;}if(D < shu[1]&& D < shu[0]){out(1);return0;}if(k &1){out(1);return0;}elseif(D >= shu[0]){out(0);return0;}else{out(1);return0;}}else{if(D >= shu[1]){out(0);return0;}if(S < shu[0]&& S < shu[1]){out(0);return0;}if(k &1){out(1);return0;}else{out(0);return0;}}return0;}
I:Game with Powers | CF 317D | 2300
题意
Game with Powers 有
n
n
n 个数字,为从
1
,
2
,
3
,
⋯
,
n
1,2,3,\cdots,n
1,2,3,⋯,n 两个人轮流选数字。如果他选了数字
i
i
i,那么也要同时拿走数字
i
2
,
i
3
,
i
4
,
⋯
i^2,i^3,i^4,\cdots
i2,i3,i4,⋯ 谁拿不了了谁就输了。 问胜负情况
1
≤
n
≤
1
0
9
1\le n\le 10^9
1≤n≤109
思路
首先容易想到,我们按照每个类 C,里面包含
c
,
c
2
,
c
3
,
⋯
c,c^2,c^3,\cdots
c,c2,c3,⋯ 容易想到,任何两个类之间的交集都为空。 所以我们可以看做是这几个类的简单游戏的
s
g
sg
sg 值的异或值就是整个游戏的
s
g
sg
sg 值。 一个类最多有
30
30
30 个元素。由于
c
c
c 并不影响这个类的
s
g
sg
sg 值,只有这个集合的大小才影响。 所以我们就是问:
s
g
(
x
)
sg(x)
sg(x) ,其中
x
=
∣
C
∣
x=|C|
x=∣C∣ 的值是多少?
按照下标来拿,如果拿了下标
i
i
i,那么要同时拿走下标
2
i
,
3
i
,
4
i
,
⋯
2i,3i,4i,\cdots
2i,3i,4i,⋯ 但是每个集合都可以拿走下标
1
1
1 来把所有的东西给拿走。看起来是一定先手必胜的,但是只能知道它的
s
g
>
1
sg>1
sg>1,并不知道具体的
s
g
sg
sg,我们怎么算呢?
容易想到,这是一个状压
d
p
dp
dp,设我们目前拿走的下标位置集合为
S
S
S,我们枚举所有下一次的转移位置
i
i
i,然后获取一个
m
e
x
mex
mex 即可。 但是复杂度
O
(
2
30
×
30
)
O(2^{30}\times 30)
O(230×30),这不是铁炸??那就打个表,反正就
30
30
30 个值呗。
但是
n
n
n 很大。我们想到,如果
x
>
s
q
r
t
(
n
)
x> sqrt(n)
x>sqrt(n),那么
∣
S
∣
=
1
|S|=1
∣S∣=1,就直接获取有多少个这样的
x
x
x 就好了。 但是要保证这个
x
x
x 并不是某一个数的几次方。
代码
时间复杂度:
O
(
n
log
n
)
O(\sqrt n\log n)
O(nlogn)
#include<bits/stdc++.h>#defineIOSios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;typedeflonglong ll;voidshow(){std::cerr << endl;}template<typename T,typename... Args>voidshow(T x,Args... args){std::cerr <<"[ "<< x <<" ] , ";show(args...);}constint MAX =1e5+50;constint MOD =998244353;constint INF =0x3f3f3f3f;const ll LINF =0x3f3f3f3f3f3f3f3f;constdouble EPS =1e-7;voidout(int mex){puts(mex?"Vasya":"Petya");}
bool vis[MAX];
map<int,int>M;int sg[50]={0,1,2,1,4,3,2,1,5,6,2,1,8,7,5,9,8,7,3,4,7,4,2,1,10,9,3,6,11,12,14};int all;intSG(int x){if(x ==0)return0;if(M[x])return M[x];int ed =(1LL<< x)-1;
map<int,int>mex;for(int i =0;i < all;++i){if((x &(1LL<< i))){int tmp = x;for(int j =1;j *(i +1)<= all;++j){int duo =(1LL<<(((i +1)* j)-1));if(tmp & duo)
tmp -= duo;}// show(x,tmp);
mex[SG(tmp)]=1;}}for(int i =0;;++i){if(!mex[i]){return M[x]= i;}}}intmain(){// for(int i = 1;i <= 40;++i)sg[i] = -1;// for(all = 1;all <= 30;++all){// cout << SG((1<<all)-1) << " ";// }
ll n;cin >> n;int ans =1;int you =1;for(ll i =2;i * i <= n;++i){if(vis[i])continue;int cnt =1;
ll tmp = i;while(tmp * i <= n){
tmp *= i;if(tmp <=100000)vis[tmp]=1;
cnt++;}
you += cnt;
ans ^= sg[cnt];// show(i,cnt,SG(cnt));}if((n - you)&1)ans ^=1;out(ans);return0;}