zoukankan      html  css  js  c++  java
  • 【XSY2988】取石子 博弈论

    题目描述

      有 (n) 堆石子,每堆石子的个数是 (c_i)

      Alice 和 Bob 轮流取石子(先后手未定),Alice 每次从一堆中取 (a) 个,Bob每次从一堆中取 (b) 个。无法操作者输。

      你要选定若干堆石子(共 (2^n))种情况,问你所有情况中:Alice 必胜的方案数;Bob 必胜的方案数;先手必胜的方案数;后手必胜的方案数。

      对 ({10}^9+7) 取模。

      (nleq 100000)

    题解

      要把每堆石子个数对 (a+b) 取模。

      为什么可以取模呢?

      首先这是一个零和博弈。

      如果 (c_igeq a+b),那么一个人操作后:如果局面对另一个人更优,那么这个人就不会这么操作。否则另一个人可以取一次这堆石子,把局面变回来。

      把这 (n) 堆按石子个数分成 (4) 类:

      1.(c_i<a)

      2.(aleq c_i<b)

      3.(bleq c_i<2a)

      4.(c_igeq 2a)

      容易发现:

      第一类是没有用的。

      如果有第二类,那么 (a) 必胜。

      如果第四类石子堆的个数 (geq 2),那么 (a) 必胜。

      如果第四类石子堆的个数 (=1) 且 第三类石子堆的个数为奇数,则 (a) 必胜。

      如果第四类石子堆的个数 (=1) 且 第三类石子堆的个数为偶数,则先手必胜。

      如果第四类石子堆的个数 (=0) 且 第三类石子堆的个数为奇数,则先手必胜。

      如果第四类石子堆的个数 (=0) 且 第三类石子堆的个数为偶数,则后手必胜。

      每一类的方案数是 (2) 的幂乘上一些组合数。

      时间复杂度:(O(n))

    代码

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cstdlib>
    #include<ctime>
    #include<utility>
    #include<functional>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int,int> pii;
    typedef pair<ll,ll> pll;
    void open(const char *s){
    #ifndef ONLINE_JUDGE
    	char str[100];sprintf(str,"%s.in",s);freopen(str,"r",stdin);sprintf(str,"%s.out",s);freopen(str,"w",stdout);
    #endif
    }
    int rd(){int s=0,c,b=0;while(((c=getchar())<'0'||c>'9')&&c!='-');if(c=='-'){c=getchar();b=1;}do{s=s*10+c-'0';}while((c=getchar())>='0'&&c<='9');return b?-s:s;}
    void put(int x){if(!x){putchar('0');return;}static int c[20];int t=0;while(x){c[++t]=x%10;x/=10;}while(t)putchar(c[t--]+'0');}
    int upmin(int &a,int b){if(b<a){a=b;return 1;}return 0;}
    int upmax(int &a,int b){if(b>a){a=b;return 1;}return 0;}
    const ll p=1000000007;
    ll fp(ll a,ll b)
    {
    	ll s=1;
    	for(;b;b>>=1,a=a*a%p)
    		if(b&1)
    			s=s*a%p;
    	return s;
    }
    int a[100010];
    int A,B,n;
    int cnt1,cnt2,cnt3,cnt4;
    ll ans1,ans2,ans3,ans4;
    ll inv[100010];
    ll fac[100010];
    ll ifac[100010];
    ll binom(int x,int y)
    {
    	return x>=y&&y>=0?fac[x]*ifac[y]%p*ifac[x-y]%p:0;
    }
    int main()
    {
    	open("a");
    	scanf("%d",&n);
    	inv[1]=fac[0]=fac[1]=ifac[0]=ifac[1]=1;
    	for(int i=2;i<=n;i++)
    	{
    		inv[i]=-p/i*inv[p%i]%p;
    		fac[i]=fac[i-1]*i%p;
    		ifac[i]=ifac[i-1]*inv[i]%p;
    	}
    	int flag=0;
    	scanf("%d%d",&A,&B);
    	if(A>B)
    	{
    		swap(A,B);
    		flag=1;
    	}
    	for(int i=1;i<=n;i++)
    	{
    		scanf("%d",&a[i]);
    		a[i]%=(A+B);
    		if(a[i]<A)
    			cnt1++;
    		else if(a[i]<B)
    			cnt2++;
    		else if(a[i]<2*A)
    			cnt3++;
    		else
    			cnt4++;
    	}
    	ans1=(ans1+(fp(2,cnt2)-1)*fp(2,cnt3+cnt4))%p;
    	ans1=(ans1+(fp(2,cnt4)-cnt4-1)*fp(2,cnt3))%p;
    	ans3=(ans3+cnt4*(cnt3?fp(2,cnt3-1):1))%p;
    	ans1=(ans1+cnt4*(cnt3?fp(2,cnt3-1):0))%p;
    	ans4=(ans4+(cnt3?fp(2,cnt3-1):1))%p;
    	ans3=(ans3+(cnt3?fp(2,cnt3-1):0))%p;
    	ans1=ans1*fp(2,cnt1)%p;
    	ans2=ans2*fp(2,cnt1)%p;
    	ans3=ans3*fp(2,cnt1)%p;
    	ans4=ans4*fp(2,cnt1)%p;
    	if(flag)
    		swap(ans1,ans2);
    	ans1=(ans1+p)%p;
    	ans2=(ans2+p)%p;
    	ans3=(ans3+p)%p;
    	ans4=(ans4+p)%p;
    	printf("%lld %lld %lld %lld
    ",ans1,ans2,ans3,ans4);
    	return 0;
    }
    
  • 相关阅读:
    华为机试:字符串翻转
    华为机试:数字颠倒
    华为机试:字符个数统计
    华为机试:提取不重复的整数
    华为机试:取近视值
    华为机试:进制转换
    华为机试:字符串分隔
    华为机试:明明的随机数
    华为机试:字符串最后一个单词的长度
    网易:相反数
  • 原文地址:https://www.cnblogs.com/ywwyww/p/9178506.html
Copyright © 2011-2022 走看看