算法笔记--博弈

(一)巴什博奕(Bash Game):只有一堆n个物品,两个人轮流从这堆物品中取物,规
定每次至少取一个,最多取m个。最后取光者得胜。
n=(m+1)r+s

(二)威佐夫博奕(Wythoff Game):有两堆各若干个物品,两个人轮流从某一堆或同
时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。
奇异局势(先手输)。前几个奇异局势是:(0,0)、(1,2)、(3,5)、(4,7)、(6,
10)、(8,13)、(9,15)、(11,18)、(12,20)
ak =[k(1+√5)/2],bk= ak + k (k=0,1,2,…,n 方括号表示取整函数)

if(n<m) swap(n,m);
int k=n-m;
n=(int)(k*(1+sqrt(5))/2.0);
if(n==m) printf("0\n");
else printf("1\n");

(三)尼姆博奕(Nimm Game):有三堆各若干个物品,两个人轮流从某一堆取任意多的
物品,规定每次至少取一个,多者不限,最后取光者得胜。

取完胜

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int main(){
    int n;
    int a[22];
    while(scanf("%d",&n)!=EOF){
        int out=0;
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
             out^=a[i];
        }
        if(out==0) printf("No\n");
        else printf("Yes\n");
    }
    return 0;
}

取完负

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>

using namespace std;

int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        int n;
        scanf("%d",&n);
        int ans=0;
        int flag=0;
        for(int i=0;i<n;i++){
            int x;
            scanf("%d",&x);
            ans^=x;
            if(x>1) flag=1;
        }
        if((ans&&!flag)||(!ans&&flag)) printf("Brother\n");
        else printf("John\n");
    }
    return 0;
}

(四)SG博奕(通用)
我们把整个博弈过程抽象为有向无环图
mex求最小非负整数mex{} = 0,mex{0,1,2,4} = 3,mex{1,2,4} = 0
sg[x] =mex{sg[y]|y是x的后继}//就是石头变少的继
这样sg就满足几个性质

  1. sg[x] == 0时,它的后继都不为零

  2. sg[x] != 0时,它的后继一定有为零的

  3. 当x点没有出边时,sg[x] == 0

这三个性质恰好与P-positon(先手必败)的性质相同:
(1).无法进行任何移动的局面(也就是terminal position)是P-position;
(2).可以移动到P-position的局面是N-position;
(3).所有移动都导致N-position的局面是P-position。
由此可知:sg[x] == 0,x就是p-position

eg-hdu3980
一个圆环N个数,每次每个人只能给连续的M个数抹漆,最后不能抹的就算输
比如(5,2) +++++,
当前边空0个时子游戏为(0,2)(3,2)
当前边空1个时子游戏有(1,2)(2,2)
当前边空2个时子游戏有(2,2)(1,2)
当前边空3个时子游戏有(3,2)(0,2)

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<queue>
#include<vector>
#include<map>
#include<stack>
#include<set>
using namespace std;
typedef long long LL;
int SG[1005];

int getSG(int n,int m){
    if(n<m) return SG[n]=0;
    if(SG[n]!=-1) return SG[n];
    bool visit[1005];// //下边递归了。。不要放全局去。。。

    memset(visit,false,sizeof(visit));
    for(int i=0;i<=n-m;i++){
        visit[getSG(i,m)^getSG(n-m-i,m)]=true;
    }
    for(int i=0;;i++){
        if(visit[i]==false){
            SG[n]=i;
            break;
        }
    }
    return SG[n];
}
int main(){
    int t;
    scanf("%d",&t);
    for(int test=1;test<=t;test++){
        int n,m;
        scanf("%d %d",&n,&m);
        memset(SG,-1,sizeof(SG));
        printf("Case #%d: ",test);
        if(n<m||getSG(n-m,m)) printf("abcdxyzk\n");
        else printf("aekdycoin\n");
    }
    return 0;
}

eg-hdu5724
给一个n*20的棋盘,先给出了一堆棋子的位置,两个人轮流着进行操作。
操作:可以将棋子移到右边的第一个空位上,(不能多隔空位)
11100之后的状态可以有。11010,10110,01110.
假如直接将这种状态看成一个数(20位,不大),所以直接状压预处理出所有状态的SG,然后N行直接多问题SG亦或。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
bool SG[2000004];
int a[24];
bool visit[24];

int pow2[22];

inline void getp(){
    pow2[0]=1;
    pow2[1]=2;
    for(int i=2;i<=20;i++) pow2[i]=pow2[i-1]*2;
}
inline void geta(int x){
    int cnt=20;
    memset(a,0,sizeof(a));
    while(x){
        a[cnt--]=x%2;
        x=x/2;
    }
}
inline int getnew(int x,int i,int j){
    return x-pow2[i-1]+pow2[j-1];
}

void getSG(){
    memset(SG,0,sizeof(SG));
    int zhuang=(1<<20)-1;
    for(int i=1;i<=zhuang;i++){
        geta(i);
        memset(visit,0,sizeof(visit));
        int k=21;
        for(int j=20;j>=1;j--){
            if(a[j]==0){
                k=j;
                continue;
            }
            if(k==21) continue;
            visit[SG[getnew(i,21-j,21-k)]]=true;
        }
        for(int j=0;;j++){
            if(visit[j]==false){
                SG[i]=j;
                break;
            }
        }
    }
}
int main(){
    getp();
    getSG();
    int t;
    scanf("%d",&t);
    while(t--){
        int n;
        int out=0;
        scanf("%d",&n);
        while(n--){
            int pp=0,w,c;
            scanf("%d",&c);
            while(c--){
                scanf("%d",&w);
                pp+=pow2[20-w];
            }
            out^=SG[pp];
        }
        if(out) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}

eg–hdu5795
两个人轮流从n堆石子里取走任意个(至少一个),或则不拿走石子,但是讲石子分成3堆。每堆至少为一个。最后没有石子去的人输。对于如果没有第二种分石子情况就是nim博弈,但是加了第二种状态后就不是了。因此对于每一堆石子都因该有一个SG值,因此直接对一堆石子打表SG然后找下规律就可以发现。当x%8==0 sg=x-1,当x%8==7 sg=x+1,其他的sg=x。所以所有堆sg亦或就是答案。

#include<cstdio>
#include<cstring>
using namespace std;

int SG[10006];
int getSG(int x){  //针对于一堆而言
    if(SG[x]!=-1) return SG[x];
    if(x==0) return 0; //0个可以输
    if(x==1) return 1; //1个可以赢

    int visit[10006];
    memset(visit,0,sizeof(visit));

    for(int i=0;i<x;i++) visit[getSG(i)]=1; //当成一堆时的可到状态

    for(int i=1;i<x;i++){  //分成3堆
        for(int j=i;j<x;j++){
            for(int k=j;k<x;k++){
                if(i+j+k==x){ //子问题的亦或是可到状态
                    visit[getSG(i)^getSG(j)^getSG(k)]=1;
                }
            }
        }
    }

    for(int i=0;;i++){
        if(visit[i]==0){
            return i;
        }
    }
}
int n;
int x;
int main(){
    /*
    memset(SG,-1,sizeof(SG));
    for(int i=1;i<100;i++){
        SG[i]=getSG(i);
        printf("%2d:%d\n",i,SG[i]);
    }*/

    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        int ans=0;
        for(int i=1;i<=n;i++){
            scanf("%d",&x);
            if(x%8==0) ans^=(x-1);
            else if(x%8==7) ans^=(x+1);
            else ans^=x;
        }
        if(ans) printf("First player wins.\n");
        else printf("Second player wins.\n");
    }
    return 0;
}

eg–hdu4111

题意:有一堆的数字,两种操作:1:将某一个数值减1、2:将两个数字合并
解法:SG[i][j]表示有i个石子数为1的堆数,其它堆合并再取完的步数为j。直接SG扫就好了。。。
有几个地方重点说一下,我最开始也是没理解,后来懂了。。
(1)if(one>=1&&!getSG(one-1,sumstep)) return SG[one][sumstep]=1;
比赛时和菊苣手动模拟了一下,其实每一种状态,不管是怎样合并或取数只要子状态里出现了先手必输的状态,因此该状态可以转移到让对手必输,所以自己必赢。。。(也就只有这个点比赛时发现了)
(2) if(one>=1&&sumstep>=1&&!getSG(one-1,sumstep+1)) return SG[one][sumstep]=1;
将一个1合并到非1的上去,其他的操作步数为什么时+1,而不是2(合并+去掉),因为+2其实就是他本身的等价状态并不是子状态。+1(去掉)才是子状态。同理类推第四种情况的时候。

#include<bits/stdc++.h>
using namespace std;
int SG[55][55000];
//SG[i][j]表示有i个石子数为1的堆数,其它堆合并再取完的步数为j。
//若值为1则先取者胜,为0为先取者输
int getSG(int one,int sumstep){
    if(SG[one][sumstep]!=-1) return SG[one][sumstep];
    //没有1的时候就是各取一个
    if(one==0&&sumstep%2==1) return SG[one][sumstep]=1;
    else if(one==0&&sumstep%2==0) return SG[one][sumstep]=0;
    //当其他的步数为1时,
    if(sumstep==1) return SG[one][sumstep]=getSG(one+1,0);

    //只要找到一种是输的其他状态。那他就能赢
    SG[one][sumstep]=0;
    //1的个数去掉一个
    if(one>=1&&!getSG(one-1,sumstep)) return SG[one][sumstep]=1;
    //非1的个数去掉一个
    if(sumstep>=1&&!getSG(one,sumstep-1)) return SG[one][sumstep]=1;
    //将1的合并到非1的上去
    if(one>=1&&sumstep>=1&&!getSG(one-1,sumstep+1)) return SG[one][sumstep]=1;
    //将两个1的合并
    if(one>=2){
        //非1的为0,相当于将一个1加到另一个1上,
        if(sumstep==0&&!getSG(one-2,sumstep+2)) return SG[one][sumstep]=1;
         //非1的为0,相当于将两个1加到非1上,
        else if(sumstep!=0&&!getSG(one-2,sumstep+3)) return SG[one][sumstep]=1;
    }
    return SG[one][sumstep];
}

int main(){
    int t;
    scanf("%d",&t);
    memset(SG,-1,sizeof(SG));
    for(int cas=1;cas<=t;cas++){
        int n;
        int one=0,sumstep=0;
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            int x;
            scanf("%d",&x);
            if(x==1) one++;
            else sumstep+=x+1;//合并,去掉
        }
        if(sumstep) sumstep--; //合并为p-1次,
        if(getSG(one,sumstep)) printf("Case #%d: Alice\n",cas);
        else printf("Case #%d: Bob\n",cas);
    }
    return 0;
}

SG 模板写法
#include<bits/stdc++.h>
using namespace std;
int SG[55][55000];
int getSG(int one,int sumstep){
    if(SG[one][sumstep]!=-1) return SG[one][sumstep];
    if(one==0&&sumstep%2==1) return SG[one][sumstep]=1;
    else if(one==0&&sumstep%2==0) return SG[one][sumstep]=0;

    if(sumstep==1) return SG[one][sumstep]=getSG(one+1,0);

    //SG[one][sumstep]=0;
    bool visit[2];
    memset(visit,false,sizeof(visit));

    if(one>=1) visit[!getSG(one-1,sumstep)]=true;
    if(sumstep>=1)visit[!getSG(one,sumstep-1)]=true;
    if(one>=1&&sumstep>=1) visit[!getSG(one-1,sumstep+1)]=true;
    if(one>=2){
        if(sumstep==0) visit[!getSG(one-2,sumstep+2)]=true;
        else visit[!getSG(one-2,sumstep+3)]=true;
    }
    if(visit[1]==true) return SG[one][sumstep]=1;//推出来的
    else return SG[one][sumstep]=0;
}

int main(){
    int t;
    scanf("%d",&t);
    memset(SG,-1,sizeof(SG));
    for(int cas=1;cas<=t;cas++){
        int n;
        int one=0,sumstep=0;
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            int x;
            scanf("%d",&x);
            if(x==1) one++;
            else sumstep+=x+1;//合并,去掉
        }
        if(sumstep) sumstep--; //合并为p-1次,
        if(getSG(one,sumstep)) printf("Case #%d: Alice\n",cas);
        else printf("Case #%d: Bob\n",cas);
    }
    return 0;
}
先看效果: https://renmaiwang.cn/s/jkhfz Hue系列产品将具备高度的个性化定制能力,并且借助内置红、蓝、绿三原色LED的灯泡,能够混合生成1600万种不同色彩的灯光。 整个操作流程完全由安装于iPhone上的应用程序进行管理。 这一创新举措为智能照明控制领域带来了新的启示,国内相关领域的从业者也积极投身于相关研究。 鉴于Hue产品采用WiFi无线连接方式,而国内WiFi网络尚未全面覆盖,本研究选择应用更为普及的蓝牙技术,通过手机蓝牙与单片机进行数据交互,进而产生可调节占空比的PWM信号,以此来控制LED驱动电路,实现LED的调光功能以及DIY调色方案。 本文重点阐述了一种基于手机蓝牙通信的LED灯设计方案,该方案受到飞利浦Hue智能灯泡的启发,但考虑到国内WiFi网络的覆盖限制,故而选用更为通用的蓝牙技术。 以下为相关技术细节的详尽介绍:1. **智能照明控制系统**:智能照明控制系统允许用户借助手机应用程序实现远程控制照明设备,提供个性化的调光及色彩调整功能。 飞利浦Hue作为行业领先者,通过红、蓝、绿三原色LED的混合,能够呈现1600万种颜色,实现了全面的定制化体验。 2. **蓝牙通信技术**:蓝牙技术是一种低成本、短距离的无线传输方案,工作于2.4GHz ISM频段,具备即插即用和强抗干扰能力。 蓝牙协议栈由硬件层和软件层构成,提供通用访问Profile、服务发现应用Profile以及串口Profiles等丰富功能,确保不同设备间的良好互操作性。 3. **脉冲宽度调制调光**:脉冲宽度调制(PWM)是一种高效能的调光方式,通过调节脉冲宽度来控制LED的亮度。 当PWM频率超过200Hz时,人眼无法察觉明显的闪烁现象。 占空比指的...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值