极小化极大;292Nim 游戏;bitset容器;464我能赢吗;486预测赢家

极小化极大:回溯+剪枝

先来说极小极大算法主要应用于什么样的游戏:
1. 零和游戏(Zero-sum Game):意思就是你死我活,一方的胜利代表另一方的失败,比如,象棋,五子棋等。
2. 完全信息(Perfect Information):玩家知道之前所有的步骤。象棋就是完全信息,因为玩家是交替着落子,且之前的步骤都能在棋盘上体现,但是石头剪子布就不是。

 

极大极小算法有些不明白 ? - 还想养只小短腿的回答 - 知乎 https://www.zhihu.com/question/27221568/answer/140874499

 

 

你和你的朋友,两个人一起玩 Nim 游戏:桌子上有一堆石头,每次你们轮流拿掉 1 - 3 块石头。 拿掉最后一块石头的人就是获胜者。你作为先手。

你们是聪明人,每一步都是最优解。 编写一个函数,来判断你是否可以在给定石头数量的情况下赢得游戏。

示例:

输入: 4
输出: false 
解释: 如果堆中有 4 块石头,那么你永远不会赢得比赛;
     因为无论你拿走 1 块、2 块 还是 3 块石头,最后一块石头总是会被你的朋友拿走。

class Solution {
public:
    bool canWinNim(int n) {
        return n%4!=0;
    }
};

bitset容器

可以下标访问,b[0]
类似数组的结构
它的每一个元素只能是0或1
每个元素仅用1bit空间
运用各种位运算,与或异或非,移位
bitset<bitNums> b1;bitNums位数据,默认补0
bitset<bitNums> b2("100101"),b3(99)
b.count();//1的个数
b.size();
b.test(index);//true:index位置为1 否则0
b.any();//true:存在1
b.all();//true:都为1
b.none();//true:没有1==都是0
b.flip(index);//对应位置取反,默认全取反
b.set(index);//对应位置置1,默认全置1
b.set(index,boolx);//对应位置boolx
b.reset(index);//对应位置0,默认全置0
b.to_string();//转sring
b.to_ulong();//转unsigned long
b.to_ullong();

在 "100 game" 这个游戏中,两名玩家轮流选择从 1 到 10 的任意整数,累计整数和,先使得累计整数和达到 100 的玩家,即为胜者。

如果我们将游戏规则改为 “玩家不能重复使用整数” 呢?

例如,两个玩家可以轮流从公共整数池中抽取从 1 到 15 的整数(不放回),直到累计整数和 >= 100。

给定一个整数 maxChoosableInteger (整数池中可选择的最大数)和另一个整数 desiredTotal(累计和),判断先出手的玩家是否能稳赢(假设两位玩家游戏时都表现最佳)?

你可以假设 maxChoosableInteger 不会大于 20, desiredTotal 不会大于 300。

示例:

输入:
maxChoosableInteger = 10
desiredTotal = 11

输出:
false

解释:
无论第一个玩家选择哪个整数,他都会失败。
第一个玩家可以选择从 1 到 10 的整数。
如果第一个玩家选择 1,那么第二个玩家只能选择从 2 到 10 的整数。
第二个玩家可以通过选择整数 10(那么累积和为 11 >= desiredTotal),从而取得胜利.
同样地,第一个玩家选择任意其他整数,第二个玩家都会赢。

class Solution {//极小化极大:超时
    vector<bool> visited;
public:
    bool canIWin(int maxChoosableInteger, int desiredTotal) {
        if(desiredTotal<=1)return true;
        //if(((1+maxChoosableInteger)*maxChoosableInteger>>1)<desiredTotal)return false;
        visited.resize(maxChoosableInteger+1,false);
        return alphaBeta(true,desiredTotal,0,1);
    }
    int alphaBeta(bool player, int target,int alpha,int beta){
        if(target<=0){
            if(player)return 0;
            else return 1;
        }
        if(player){            
            for(int i=1;i<=visited.size();++i){
                if(!visited[i]){
                    visited[i]=true;
                    int temp=alphaBeta(!player,target-i,alpha,beta);
                    visited[i]=false;
                    if(temp>alpha){
                        alpha=temp;                        
                    }
                    if(alpha>=beta)
                        return alpha;                    
                }
            }
            return alpha;
        }
        else{
             for(int i=1;i<=visited.size();++i){
                if(!visited[i]){
                    visited[i]=true;
                    int temp=alphaBeta(!player,target-i,alpha,beta);
                    visited[i]=false;
                    if(temp<beta){
                        beta=temp;                        
                    }
                    if(alpha>=beta)
                        return beta;                    
                }
            }
            return beta;
        }
        
    }
};

 

//极小化极大,改进:bitset+合并player(1表示先手,0表示后手)+map
//返回值1表示先手能赢,0表示先手输
//player1时,存在一个返回值为true则true,否则false        或运算
//player0时,全部都返回true才true,否则false(存在一个返回值为false)    与运算
class Solution {
    std::unordered_map<unsigned long, bool> mp;
    std::bitset<25> bs;
    int maxN;
public:
    bool canIWin(int maxChoosableInteger, int desiredTotal) {
        if(desiredTotal<=1)return true;
        if(((1+maxChoosableInteger)*maxChoosableInteger>>1)<desiredTotal)return false;
        maxN=maxChoosableInteger;
        return alphaBeta(1,desiredTotal);
    }
    int alphaBeta(bool player, int target){
        if(target<=0)
            return !player;
        unsigned long bsMap=bs.to_ulong();
        if (mp.count(bsMap)) 
            return mp[bsMap]; 
        for(int i=1;i<=maxN;++i){
            if(bs[i])continue;
            bs[i]=true;
            if(player==alphaBeta(!player,target-i)){
                bs[i]=false; 
                return mp[bsMap] = player;
            }
            bs[i]=false;             
        }
        return mp[bsMap] = !player;
    }
};
class Solution {//评论区答案
public:
    int maxChoosableInteger, desiredTotal;
    std::unordered_map<unsigned long long, bool> mp;
    std::bitset<25> bs;
    // 当前这个状态下, 能否稳赢. total_sum记录当前的和, bitset记录那些数已经被选择过了
    bool dfs(int total_sum) {
        // 递归的边界
        if (total_sum >= desiredTotal) return true;
        // 记忆化
        if (mp.find(bs.to_ullong()) != mp.end()) return mp[bs.to_ullong()];    
        
        // 假设当前状态是A
        // 这里就是上文说的, 枚举B走的所以情况的(用一个for), 只有循环里B都不能稳赢, A才能稳赢. 
        for (int i = 1; i <= maxChoosableInteger; ++i) {
            if (bs[i]) continue;            
            bs[i] = 1;
            // 这里就是枚举B 
            if (dfs(total_sum + i)) {
                bs[i] = 0;
                return mp[bs.to_ullong()] = false;
            }
            bs[i] = 0;
        }
        return mp[bs.to_ullong()] = true;
    }

    bool canIWin(int a_, int b_) {
        maxChoosableInteger = a_;
        desiredTotal = b_;
        if(b_<=1)return true;
        // 所有数加起来都小于desiredTotal, 则稳输
        if ((maxChoosableInteger + 1) * maxChoosableInteger * 0.5 < desiredTotal)
            return false;
        for (int i = 1; i <= maxChoosableInteger; ++i) {
            bs[i] = 1;
            if (dfs(i)) return true;
            bs[i] = 0;
        }
        return false;
    }
};

给定一个表示分数的非负整数数组。 玩家1从数组任意一端拿取一个分数,随后玩家2继续从剩余数组任意一端拿取分数,然后玩家1拿,……。每次一个玩家只能拿取一个分数,分数被拿取之后不再可取。直到没有剩余分数可取时游戏结束。最终获得分数总和最多的玩家获胜。

给定一个表示分数的数组,预测玩家1是否会成为赢家。你可以假设每个玩家的玩法都会使他的分数最大化。

示例 1:

输入: [1, 5, 2]
输出: False
解释: 一开始,玩家1可以从1和2中进行选择。
如果他选择2(或者1),那么玩家2可以从1(或者2)和5中进行选择。如果玩家2选择了5,那么玩家1则只剩下1(或者2)可选。
所以,玩家1的最终分数为 1 + 2 = 3,而玩家2为 5。
因此,玩家1永远不会成为赢家,返回 False。


示例 2:

输入: [1, 5, 233, 7]
输出: True
解释: 玩家1一开始选择1。然后玩家2必须从5和7中进行选择。无论玩家2选择了哪个,玩家1都可以选择233。
最终,玩家1(234分)比玩家2(12分)获得更多的分数,所以返回 True,表示玩家1可以成为赢家。


注意:


    1 <= 给定的数组长度 <= 20.
    数组里所有分数都为非负数且不会大于10000000。
    如果最终两个玩家的分数相等,那么玩家1仍为赢家。

//最小化极大,没有记忆线索
class Solution {   
    vector<int> nums;
public:
    bool PredictTheWinner(vector<int>& nums) {
        if(nums.size()<=2)return true;
        this->nums=nums;
        return helper(1,0,nums.size()-1,0);
    }
    bool helper(bool player,int le,int ri,int sum){
        if(le>ri)
            return sum>=0;
        if(player)
            return helper(!player,le+1,ri,sum+nums[le])|helper(!player,le,ri-1,sum+nums[ri]);        
        else
            return helper(!player,le+1,ri,sum-nums[le])&helper(!player,le,ri-1,sum-nums[ri]);        
    }
};
//最小化极大,记忆话线索(bistset+map)
class Solution {
    bitset<20> bs;
    vector<int> nums;
    unordered_map<unsigned long,int>mp;
public:
    bool PredictTheWinner(vector<int>& nums) {
        if(nums.size()<=2)return true;
        this->nums=nums;
        return helper(1,0,nums.size()-1,0);//player1表示先手,0表示后手
    }
    bool helper(bool player,int le,int ri,int sum){//返回1表示先后玩家赢
        if(le>ri)
            return sum>=0;
        int bsMap=bs.to_ulong();
        if(mp.count(bsMap))return mp[bsMap];
        if(player){//存在一true即为true
            bs[le]=1;
            if(helper(!player,le+1,ri,sum+nums[le])){
                bs[le]=0;
                return bs[bsMap]=true;
            }
            bs[le]=0;

            bs[ri]=1;
            if(helper(!player,le,ri-1,sum+nums[ri])){
                bs[ri]=0;
                return bs[bsMap]=true;
            }
            bs[ri]=0;

            return bs[bsMap]=false;
        }
        else{//存在一false即false
            bs[le]=1;
            if(!helper(!player,le+1,ri,sum-nums[le])){
                bs[le]=0;
                return bs[bsMap]=false;
            }
            bs[le]=0;

            bs[ri]=1;
            if(!helper(!player,le,ri-1,sum-nums[ri])){
                bs[ri]=0;
                return bs[bsMap]=false;
            }
            bs[ri]=0;

            return bs[bsMap]=true;
        }

    }
};
//改用[le,ri]记忆化线索
//但测试用例[1,2,99]报错,未发现原因
class Solution {   
    vector<int> nums;
    vector<vector<int>>visited;
public:
    bool PredictTheWinner(vector<int>& nums) {
        int nSize=nums.size();
        if(nSize<=2)return true;
        this->nums=nums;
        visited.resize(nSize,vector<int>(nSize,-1));
        return helper(1,0,nSize-1,0);
    }
    bool helper(bool player,int le,int ri,int sum){
        if(le>ri)
            return sum>=0;
        if(visited[le][ri]!=-1)return visited[le][ri];
        if(player){
            if(helper(!player,le+1,ri,sum+nums[le]))
                return visited[le][ri]=true;
            if(helper(!player,le,ri-1,sum+nums[ri]))
                return visited[le][ri]=true;
            return visited[le][ri]=false;
        }
        else{
            if(!helper(!player,le+1,ri,sum-nums[le]))
                return visited[le][ri]=false;
            if(!helper(!player,le,ri-1,sum-nums[ri]))
                return visited[le][ri]=false;
            return visited[le][ri]=true;
        }

    }
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值