闲来无事刷水题、简单博弈论专题、sg函数、洛谷

今天闲来无事,不想刷codeforces了,到洛谷提高组训练营找几道水题刷着玩玩(虽然自己早已过了打OI的年纪)~

简单博弈论专题

P1199 三国游戏

这么考虑,由于电脑总是不能让我搭配出当前能搭配出的最大的组合,也就是说我不管选哪个英雄,都不可能匹配出该英雄能搭配出的最大的默契值,那么我最大能搭配出的默契值就是所有英雄匹配中,次大默契值的最大值。
假设有 abcd a 、 b 、 c 、 d 四位英雄
a a 能匹配的最大值为aM次大值为 am a m
b b 能匹配的最大值为bM次大值为 bm b m
c c 能匹配的最大值为cM次大值为 cm c m
d d 能匹配的最大值为dM次大值为 dm d m

假设其中 am>bm>cm>dm a m > b m > c m > d m

那么我能得到的最大可能值也就是 am a m 了。
下面证明,电脑能组合出的最大值一定比 am a m 要小。
那么我一开始选择 a a ,电脑一定会选择与a能形成 aM a M 的值的英雄,假设是 d d
然后我再选c,这里假设 caam c 与 a 组 合 得 到 a m
那么电脑会选择 b b
反证法开始了:
假设dbt>am>dm
daaM>am>dm d 与 a 形 成 的 默 契 值 为 a M > a m > d m
这样的话 dm d m 不可能是 d d 能匹配到的次大值,矛盾。
所以t<am
本次电脑不会选择到比 am a m 更大的数。
随后我们只需要破坏掉电脑构造成最大的组合就可以了。

P1288 取数游戏II

如果出发点靠着0边,那么只能往一个方向走,并且走过的边的值一定要减到0,不然对面会逆过来,然后把边减到0,这样我方就输了。

不靠0边的时候,如果沿着某个方向走会赢,那么就把沿路经过的边设置为0。
如果沿着那个方向走都不会赢,你任取一个方向走的时候不把边设置为0,对方在下一次走的时候也会继续沿着这个方向,并把下一条边设置成0,你还是要一直沿着这个方向走。

所以不管怎么样,经过的边都会变成0,也就是说一旦选定方向了,就只能一直走。
所以结果很固定,你只要把2个方向都老老实实沿着走下去,有一种情况能赢就能赢,否则就不能赢。

P1290 欧几里德的游戏

典型的辗转相除法。

假设这时候状态是 (a,b)a>b ( a , b ) 满 足 a > b
如果 [a/b]=1 [ a / b ] = 1 那么 (a,b) ( a , b ) (b,a%b) ( b , a % b ) 的状态是相反的。
如果 [a/b]=k>1 [ a / b ] = k > 1 那么就是必胜的,因为如果 (b,a%b) ( b , a % b ) 是必败态,那么a直接剪去k个b,否则的话,a剪去k-1个b。很简单是不是。

P2148 [SDOI2009]E&D

山东的省选题,中规中矩。
由于多个组是独立游戏,所以Nim博弈,直接求出所有组的sg函数,然后异或就可以了,如果异或值为0则必败,否则必胜。
由于每个二元组 (a,b) ( a , b ) 的范围都是 1e9 1 e 9 ,这样的话,sg函数的复杂度太大了,所以要想想办法。
方法就是:打表找规律!2333
规律如下:

int sg(int x,int y){
    if(x == 1 && y == 1)
        return 0;
    if(x%2 && y%2)
        return 0;
    if(x%2) swap(x,y);
    if(y%2 == 0){
        return sg(x/2,y/2)+1;
    }
    else{
        return sg(x,y+1);
    }
}

P1247 取火柴游戏

简单的不像话,跟上面的题难度一样,方法也一样,求sg函数,然后异或。
不同的地方是,这个要求第一步的方案。

在必胜态的时候,异或得到的值为ans。
我们第一步要做的就是拿掉一堆中的某些火柴,然后使异或值变为0。
假设我们要拿掉第i堆的某些火柴,那么这一堆应该剩下的火柴的数目就是 ans a n s xor n[i] n [ i ] ,拿走的火柴数目就是 n[i] n [ i ] − ( ans a n s xor n[i] n [ i ] )。
从小到达枚举堆数,找一个合法的情况就好了。

P2575 高手过招

这到也是sg函数➕异或。
不得不说sg函数是博弈论的大杀器呀。
求sg函数的方法就不说了,注意的点:

  • 这题要用到位运算,压缩状态,不然时间、空间可能不够。
  • 开O2!!!
代码
#include <iostream>
#include <cstdio>
#include <set>
#include <cstring>
#include <unordered_map>
using namespace std;
const int maxn = 2e6;
int T,n,m;
int sg[maxn];
int grundy(int status){
    if(sg[status] != -1)
        return sg[status];
    unordered_map<int,int> mp;
    for(int i = 19;i > 0;--i){
        if(!(status&(1<<i))) continue;
        int j = i-1;
        for(;j >= 0;--j)
            if(!(status&(1<<j)))
                break;
        if(j == -1) continue;
        mp[grundy(status ^ ((1<<i) + (1<<j)))] = 1;;
    }
    for(int i = 0;;i++){
        if(!mp[i])
            return sg[status] = i;
    }
}
int main(){
    cin>>T;
    memset(sg,-1,sizeof(sg));
    while(T--){
        scanf("%d",&n);
        int ans = 0;
        for(int i = 1;i <= n;++i){
            scanf("%d",&m);
            int status = 0;
            for(int j = 1;j <= m;++j){
                int p;
                scanf("%d",&p);
                status |= 1<<(20-p);
            }
            ans ^= grundy(status);
        }
        if(ans) 
            puts("YES");
        else 
            puts("NO");
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值