取石子’s 题解

来自信息学奥赛一本通T1816
题面描述:
n n n 堆石子,第 i i i 堆石子有 x [ i ] x[i] x[i] 个。
Alice和Bob轮流取石子,Alice先手,每次从一堆石子中取 a ∼ b a∼b ab 个。无法操作的人失败。此外,当一个人取完一堆石子时他会立即获胜。求谁会获胜。
有多组数据。
基本的博弈论的升级版???
显然这题不能直接把sg函数异或起来了,开始分类讨论。
前三个部分分口胡一下(后面两个没想过,因为有点麻烦):
sub1:
n = 1 n=1 n=1,直接一个简单小规律。
sub2:
a = b a=b a=b,直接暴力规律。
sub3:
a = 1 a=1 a=1,那和正常的没什么区别,直接勇。
sub4和sub5我认为推规律和正解没什么区别了
好了。。。我也没写代码,不确定对不对,dalao们可以自己试试(其实只是因为我懒 )。
那么开始正解:
很明显这只是比正常呃博弈论多了点限制嘛(不过这样就不能一个个子游戏算出来再异或了呢)。
有点烦呢。。。不过试试分类讨论???
显然如果存在 x ∈ [ a , b ] x \in [a,b] x[a,b],那么一开始直接先手必胜,特判掉。
那么接下来的如果不满足先手特判,那么即可求它的sg函数开始异或。
显然为了让对方下次操作不会赢,我们这次操作必定不会使我们操作后那堆石头的数量在 [ a , b ] [a,b] [a,b],那么我们就可以把这段区间设为不可到达的状态,然后我们不难发现,其实sg函数就是取一段区间的mex,它是存在循环节的。
提一下:
m e x mex mex 运算: m e x ( S ) = m i n x ∈ N , x ∉ S mex(S)=min_{x\in N,x\not\in S} mex(S)=minxN,xS{ x x x}(相当于线性基最小无关位)
之后分三种情况:
1、对于 x < a xx<a 的,显然 s g ( x ) = 0 sg(x)=0 sg(x)=0
2、如果 a = 1 a=1 a=1,那么就和正常的博弈没什么区别了,那么显然 s g ( x ) = x % ( a + b ) sg(x)=x\%(a+b) sg(x)=x%(a+b)
3、如果 a > 1 a>1 a>1,可以推导出从 b + 1 b+1 b+1 开始依次下去为 a − 1 a-1 a1 个1, a a a 个0, a a a 个2, a a a 个3…(最后一个可能不足 a a a 个)每 a + b a+b a+b 的长度一个循环(第一个循环的长度为 a + b − 1 a+b-1 a+b1),那么我们可以先把当前石子去掉一个 b b b,然后 % ( a + b ) \%(a+b) %(a+b) 来求它的sg函数,然后你就会发现会多余一个0,再深入思考一下,发现0其实就是说明当前为 b b b 个,这时的sg函数也为1,那么就很好办了,求sg函数只要算一下 ( x − b ) % ( a + b ) / a (x-b)\%(a+b)/a (xb)%(a+b)/a,如果等于0改为1,如果等于1改为0,另外都等于这个值。
最后判断输出即可。
算sg函数近乎 O ( 1 ) O(1) O(1),所以总的时间复杂度为 O ( t n ) O(tn) O(tn)
code:

#include 
using namespace std;
int t,n,a,b,x,sum,fl,num;
void init(){
	sum=fl=0;
	scanf("%d%d%d",&n,&a,&b);
	num=a+b;//模数提前算出来了 
}
int sg(int x){//求sg函数 
	if(x<a) return 0;
	if(a==1) return x%num;
	(x-=b)%=num;
	return x/a>1?x/a:(x/a)^1;
}
void work(){
	for(int i=0;++i<=n;
		scanf("%d",&x),x>=a&&x<=b?fl=1:sum^=sg(x));//fl-->先手特判 
}
void prin(){
	printf(fl||sum?"Alice\n":"Bob\n");
}
int main(){
	for(scanf("%d",&t);t--;init(),work(),prin());
	return 0;
} 

感谢各位dalao捧场

你可能感兴趣的:(题解,博弈论)