牛客网暑期ACM多校训练营(第八场)H-Playing games dp(滚动数组)

链接:https://www.nowcoder.com/acm/contest/146/H
来源:牛客网
 

Playing games

时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld

题目描述

Niuniu likes playing games. He has n piles of stones. The i-th pile has ai stones. He wants to play with his good friend, UinUin. Niuniu can choose some piles out of the n piles. They will play with the chosen piles of stones. UinUin takes the first move. They take turns removing at least one stone from one chosen pile. The player who removes the last stone from the chosen piles wins the game. Niuniu wants to choose the maximum number of piles so that he can make sure he wins the game. Can you help Niuniu choose the piles?

输入描述:

 

The first line contains one integer n (1 ≤ n ≤ 500000), which means the number of piles.
The second line describes the piles, containing n non-negative integers, a1 a2 … an, separated by a space. The integers are less than or equal to 500000.

输出描述:

Print a single line with one number, which is the maximum number of piles Niuniu can choose to make sure he wins. If Niuniu cannot always win whatever piles he chooses, print 0.

示例1

输入

8
1 9 2 6 0 8 1 7

输出

7

说明

Niuniu can choose the piles {1,9,6,0,8,1,7} to make sure he wins the game.

题意:niuniu和uinuin玩取石子的游戏,取石子的规则如下:两人轮流从石子堆中取石子,每次只能取选定的一堆中的石子并且至少取一个。现在由n堆石子,要取的石子堆由niuniu来选择(从n堆石子中取任意堆),取完后由uinuin开始先取,问niuniu最多可以选多少堆使得自己能赢。

做法:

       首先讨论一下,在选定的石子中取如何才能后手赢。即在k堆石子中只能从一堆中取任意多个(至少一个)该如何才能赢。将所有的数进行异或,如果不为0,则先手必胜。加入异或后的数为1001,则一定存在一堆石子中至少有1000(即8)个石子,在这至少8个的石子堆中取0001(即1),则剩下的数sum异或为0,后手无论取什么都会将sum破坏为有1的数,先手再次进行相同操作,则最后取到0时,一定是先手。

       那么如果niuniu想赢,必须在保证所有的数异或为0的情况下取最多的数。

       用dp来做,第一维要设置成滚动数组,不然空间会超。dp[i][j]表示在状态i之下,异或和为j需要取的最少石子数,则答案就是n-dp[now][sum].有较快的分类,具体见代码。

        解释:如果n很大,那么很容易被第一个分类和第二个分类给筛过去,不会进行dp的过程,所以dp的时候复杂度不会很大,很精妙啊。


       

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int maxn =600005;
const int inf=0x3f3f3f3f;
vector<int> app;
int a,sum,temp,vis[maxn],dp[2][maxn],pos,n;//dp[i][j]表示在i状态下异或和为j取的最少个数
int main(){
    memset(dp,inf,sizeof(dp));
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a);
        sum^=a;
        if(!vis[a]) app.push_back(a);
        dp[0][a]=1;
        vis[a]++;
    }
    if(sum==0){
        printf("%d\n",n);
        return 0;
    }
    dp[0][0]=0;
    if(vis[sum]){//如果存在sum,则只要不取该数sum,剩余的数异或为0
        printf("%d\n",n-1);
        return 0;
    }
    for(int i=0;i<app.size();i++){
        if(vis[sum^app[i]]){//同理,只要不取app[i]和sum^app[i]两个数,剩余的数异或为0
            printf("%d\n",n-2);
            return 0;
        }
    }
    for(int i=0;i<app.size();i++){
        pos^=1;
        for(int j=0;j<maxn;j++){
            if(dp[pos^1][j]>=inf) continue;
            dp[pos][j]=min(dp[pos^1][j],dp[pos][j]);
            dp[pos][j^app[i]]=min(dp[pos^1][j]+1,dp[pos][j^app[i]]);//转移方程
        }
    }
    printf("%d\n",max(n-dp[pos][sum],0));
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_41955236/article/details/81590531