牛客 取石子游戏
题意:
数据范围:n<=1e18
思路:
首先应该能看出当前石子为k个的时候,其实就是分成k/2和k-k/2两分
然后就是:
1的时候必败,
2的时候分成1和1,拿走1剩下1,必胜,
3的时候分成1和2,拿走2上下1,必胜,
4的时候分成2和2,剩下2,必败
…
用必胜态和必败态手推15个左右,就会发现:
必胜是一段连续的,必败是一段连续的。
1必败
2必胜
3必胜
接下来轮到4=2+2,5=2+3,6=3+3,这三个必败,因为两份都是必胜态,因此可以推出:
假设必胜连续m次,那么接下来必败连续m+(m-1)次,其中m次是自加(例如2+2,3+3),(m-1)次是相邻(例如2+3)
因为两份都是必胜才是必败
假设必败连续m次,那么接下来必胜连续m+(m-1)次,其中m次自加,(m+1)次是相邻
因为两份只要有一份是必败就是必胜,所以必败的相邻是(m+1)而不是(m-1),
例如3=1+2,1是必败,2是必胜,可以推出3是必胜
因为n<=1e18,而每一段连续的要不就是连续m+(m-1)次,要不就是连续m+(m-1)次,基本上是翻倍的,
因此计算60次左右就到1e18了,直接预处理出每一段的长度,
对于每组循环,二分出给定的n应该在哪一段,根据那一段是必胜还是必败就可以知道答案了。
code:
//https://ac.nowcoder.com/acm/contest/4743/D
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=2e5+5;
vector<int>a{0,1,2},b{0,0,1},c{0,1,3};//a存一段的长度,b存对于的必胜必败态,c存a的前缀和(用于二分)
void init(){
int len=a.size();
int sum=c[len-1];
while(sum<=1e18){
int t=a[len-1];
int tt=b[len-1];
int aa=0;
if(tt){
aa=t-1+t;
}else{
aa=t+t+1;
}
sum+=aa;
a.push_back(aa);
b.push_back(tt^1);
c.push_back(sum);
len++;
}
}
signed main(){
init();
int len=c.size();
int T;
cin>>T;
while(T--){
int n;
cin>>n;
int l=0,r=len-1;
int ans=0;
while(l<=r){
int mid=(l+r)/2;
if(n>c[mid]){
ans=mid;
l=mid+1;
}else{
r=mid-1;
}
}
ans++;
if(b[ans]){
cout<<"XiaoHuiHui"<<endl;
}else{
cout<<"XiaoQiao"<<endl;
}
}
return 0;
}