• qbzt day6 上午


    还是合并石子,但是这次可以任意两个合并,并且求最大异或和

     

    f[s]表示把s所对应的的石子合并为一堆的最小代价

    最后求f[2^n-1]

    怎么转移?

    最后一次也是把两堆合并成一堆,但是会有很多情况,可以枚举s的所有子集,把子集和剩下的部分合并

    直接枚举比s小的数,看看是否s|a=s或s&a=a

    复杂度O(4^n)

    优化:如果自己直接枚举子集就会快一些

    cin >> n;
        for (int a=0;a<n;a++)
            cin >> z[a];
        for (int a=0;a<(1<<n);a++)
            for (int b=0;b<n;b++)
                if (a & (1<<b))
                    sum[a] += z[b];
        
        memset(f,0x3f,sizeof(f));
        for (int a=0;a<n;a++)
            f[1<<a] = 0;
            
        /*for (int s=0;s<(1<<n);s++)
            for (int a=1;a<s;a++)
                if ( (s|a) == s)
                    f[s] = min(f[s],f[a]+f[a^s]+(sum[a]^sum[a^s]));*/
                    
        for (int s=0;s<(1<<n);s++)
            for (int a=(s-1)&s;a;a=(a-1)&s)
                f[s] = min(f[s],f[a]+f[a^s]+(sum[a]^sum[a^s]));
                    
        cout << f[(1<<n)-1] << endl;

     

    复杂度O(3^n)

    注意枚举子集的方法(很重要)

     

    博弈论DP

     

    有一个游戏G,两个人玩(van♂),回合制,没有平局,胜负的区分方法是当某个人没办法进行操作的时候就输了

     

    例:

    有一个整数S(2<=S<=200),先手在S上减掉一个数x,至少是1,但小于S。之后双方轮流把S减掉一个正整数,但都不能超过先前一回合对方减掉的数的k倍,减到0的一方获胜。问先手是否必胜?

    怎么dp?

    一个游戏G有很多的状态,在一个状态下如果采用某种操作,可以把一个状态转移到另一个状态。如果没有状态可以转移了或者是转移到的都是必胜态,就终止了,这个状态叫做  必败态  意思是:谁在这个状态进行操作,谁就一定会输

    对应的,能够转移到必败态的状态就是必胜态

    f[s]代表游戏的某个状态必胜还是必败

    如果存在f[s[[i]]=false,则有f[s]=true  如果对于所有的f[s[i]]=true,则f[s]=false

    和什么有关?目前剩下的,队手上次减的。f[i][j]表示s当前还剩下i,对手上一次减的是j的情况必胜还是必败

    枚举1<=r<=k*j,则可以转移到f[i-r][r]

    用记忆化搜索

    #include<iostream>
    
    using namespace std;
    
    bool f[][],g[][];
    
    bool dfs(int i,int j)
    {
        if (i==0) return false;
        if (g[i][j]) return f[i][j];
        g[i][j]=true;f[i][j]=false;
        for (int r=1;r<=i && r<=k*j;r++)
            if (dfs(i-r,r) == false) f[i][j]=true;
        return f[i][j];
    }
    
    int main()
    {
        cin >> s >> k;
        for (int a=1;a<s;a++)
            if (dfs(s-a,a) == false)
            {
                cout << "Alice" << endl;
                return 0;
            }
        cout << "Bob" << endl;
        
        return 0;
    }

     

    现在有n个游戏,怎么搞?

    例:取石子游戏

    有n堆石子,每次可以从某一堆石子里面取走任意多的石子。当谁没有办法取石子时就输了。先手必胜还是必败?

     

    假如现在只有一堆石子

    SG函数

    定义sg[0]=0  sg[i]!=0表示i是必胜态   sg[i]=0表示i是必败态

    sg[1]:找到1能转移到的所有状态,把他们的sg函数都写出来,在找到最小的没有出现过的自然数

    对于sg[n],找到最小的没有在sg[0],sg[1]...sg[n-1]中出现的自然数,这个数就是sg[n]的值

    sg函数有什么用?

    回到这个问题,我们发现sg函数只对一个游戏求,但是这个题是多个游戏,怎么把一个变成n个?

    sg定理:n个游戏的sg值等于每个游戏的sg值异或起来

    证明:拿头证

     

    然后这个题经过手推发现sg[n]=n,这样直接搞就好了

     

    int main()
    {
        cin >> n;
        int ans=0;
        for (int a=1;a<=n;a++)
        {
            int v;
            cin >> v;
            ans = ans ^v;
        }
        if (ans!=0) cout << "Alice" << endl;
        else cout << "Bob" << endl;
    }

     

    例:给你n堆石子,每一次可以在某一堆石子中取走1~4个石子,问先手必胜还是必败

    列出来sg函数找找规律,发现sg[i]=i%5,然后抑或起来就好了

     

    一般这种题就是自己手算出sg函数然后抑或起来就好了

     

     

    例:n+1堆石子,最左边一堆石头有2012个,两个人分别进行操作。一次操作可以选取两堆不同的石堆分别增加或减少一个石子(一加一减,或给已经不剩石子的堆加一个都是允许的)。为了保证游戏会在有限步内结束,规定所选的两堆中右边的那一堆一定要包含奇数个石子,无路可走者输.问先手是否必胜?

     

    这个题非常恶心的一点就是不同的堆有联系,所以考虑把他转化成最基本的问题

    先看每一堆是奇数个还是偶数个,答案就是把所有奇数的下标取出来异或,看是否等于0

    有N堆石子放在N级楼梯上,楼梯编号为0..N-1,每堆有a[n]个石子。两人轮流游戏,每次将任意堆中的任意个石子移动到它下面那一层楼梯上,0号的石子不能动。直到所有石子都移动到0号,那个人就赢了。问先手是否有必胜策略 。

     

    把所有下标为奇数的石子数量异或起来

    因为如果把偶数位置的石子扔到奇数上,那么我只需要把奇数的位置移到下一个偶数就好了

    所以偶数位置上的石子对答案没有影响,且奇数位置上的石子搬到偶数上后就没有影响了

    例:N*M的棋盘,每个格子有一定数量棋子,每次可将某个格子部分或全部棋子向右或向下移动,问先手必胜还是必败。

     

    和上个题差不多,只需要把所有距离为奇数的点的距离异或起来就行了

    因为如果从偶数移动到奇数,一定可以从奇数移动到偶数,反之则不然

     

    例:1xN(1<=N<=2000)的空格子,双方轮流操作,每次选一个没有被标记的格子,将其标记,如果某人操作完后,存在3个连续的格子都被标记了,那么他就获胜了,问先手是否有必胜策略?

     

    如果把他当成一个游戏,我们需要搞一个2000位的二进制数,存都存不下

    考虑把他当成多个游戏

    如果我们先手在一个位置打标记,后手就不能在这个位置左两个右两个打标记

    状态sg[i]表示对于一个长度为i的横条它的sg是多少

    Vector存所有能转移到的sg值

    枚举染色点,把原来的横条分成两个小的横条,算出来左边和右边的长度

    怎么计算mex?

    先排序,从0,1,2,3这样一直看,直到找到一个没有的。为了避免数组越界,直接加一个很大的数就可以了

    #include<iostream>
    #include<algorithm>
    #include<vector>
    
    using namespace std;
    
    int dfs(int n)
    {
        if (n==0) return 0;
        if (f[n]) return sg[n];
        f[n]=true;
        vector<int> z;
        for (int a=1;a<=n;a++)
        {
            int l = max(a-3,0);
            int r = max(n-a-2,0);
            z.push_back( dfs(l) ^ dfs(r) );
        }
        sort(z.begin(),z.end());
        z.push_back(233333333);
        
        for (int a=0,p=0;;a++)
        {
            if (z[p] != a)
            {
                sg[n] = a;
                return sg[n];
            }
            while (z[p]==a)
                p++;
        }
    }
    
    int main()
    {
        cin >> n;
        if (dfs(n)) cout << "Alice" << endl;
        else cout << "Bob" << endl;
    }

    看看能不能有一种情况转移到必败态

    但是开30000*30000的数组就炸了

    怎么优化

    把状态改成f[a][b][y],因为x只可能是2的倍数或者3的倍数

     

  • 相关阅读:
    第二章 数据类型、变量、和运算符
    第一章
    ActiveMQ点对点的发送和接收
    初探设计模式(1)——工厂模式
    IDEA使用switch传入String编译不通过
    MyBatis日期转换报错
    HTML页面传值问题
    maven配置本地仓库
    Maven的下载及安装
    PHP实现获得一段时间内所在的所有周的时间
  • 原文地址:https://www.cnblogs.com/lcezych/p/11206638.html
Copyright © 2020-2023  润新知