[牛客网暑期ACM多校训练营(第八场)] H Playing games [二分+FWT]

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.

分析:根据 NIM博弈我们可以知道,题目是想从n个数字中找最多的数字个数让其xor为0, 我们转化一下问题,如果知道n个数字的xor值为x,那么就是找最少的数字个数让其xor为x, 然后我们可以二分这个个数,用FWT验证是否有这个方案.

#include <map>
#include <set>
#include <queue>
#include <stack>
#include <list>
#include <string>
#include <math.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
#define rep(i, l, r)  for(int i = l; i < r; i++)
#define per(i, r, l) for(int i = r; i >= l; i--)

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int>pii;

const int N = (int) 2e6 + 11;
const int M = (int) 1e6 + 11;
const int MOD = (int) 1e9 + 7;
const double EPS = (double) 1e-9;
const double PI = (double)acos(-1.0);
const int INF = (int) 0x3f3f3f3f;
const ll INFF = (ll) 0x3f3f3f3f3f3f3f3f;

void read(int &x){
    char ch = getchar(); x = 0;
    while(ch < '0' || ch > '9') ch = getchar();
    while(ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
}
/*-----------------------------------------------------------------------------------*/ 

ll add(ll x, ll y){return (x += y) >= MOD ? x - MOD : x;}
ll sub(ll x, ll y){return (x -= y) < 0 ? x + MOD : x;}

int Pow(int a, int b, int c){
    int s = 1; a %= c;
    while(b){
        if(b & 1) s = s * 1ll * a % c;
        b >>= 1;
        a = a * 1ll * a % c;
    }
    return s;
}
int inv2 = Pow(2, MOD - 2, MOD);
void FWT_xor(ll a[], int n, int on) {
    for(int i = 1; i < n; i <<= 1) {
        for(int j = 0; j < n; j += (i << 1)) {
            for(int k = 0; k < i; ++k) {
                int u = a[j + k], t = a[j + k + i];
                a[j + k]=add(u, t); a[j + k + i] = sub(u, t);
                if(on == -1) {
                    a[j + k] = (ll)a[j+k] * inv2 % MOD;
                    a[j + k + i] = (ll)a[j + k + i] * inv2 % MOD;
                }
            }
        }
    }
}

ll a[N], b[N]; int mx =(1 << 19);
bool Judge(int cnt, int v){
    rep(i, 0, mx) 
        b[i] = Pow(a[i], cnt, MOD); 
    FWT_xor(b, mx, -1);
    b[v] = (b[v] % MOD + MOD) % MOD;
    return (b[v] > 0);
}

int main(){
    #ifndef ONLINE_JUDGE
        freopen("in.txt", "r", stdin);
        freopen("out.txt", "w", stdout);
    #endif
    //cout << inv2 <<"\n";
    //cout << (1 << 19) <<"\n";
    int n; scanf("%d", &n);
    int val; int v = 0; 
    rep(i, 0, n){
        scanf("%d", &val);
        a[val]++;
        v ^= val;
    }
    if(v == 0) printf("%d\n", n);
    else{
        a[0] = 1; // 为了保证二分的答案的次数大于二的时候,根据FWT的数学含义,我们必须要保留最初的状态
        FWT_xor(a, mx, 1);
        int le = 1, ri = 20, ans = 0;
        while(le <= ri){
            int mid = (le + ri) >> 1;
            if(Judge(mid, v)) {
                ans = mid; ri = mid - 1;
            }else le = mid + 1;
        }
        //cout << ans <<"\n";
        printf("%d\n", n - ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值