牛客网暑期ACM多校训练营(第八场)H——Playing games FWT优化dp

9 篇文章 0 订阅
7 篇文章 0 订阅

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

题目描述

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.

 

题意

有N堆石头,NiuNiu要从里面选几堆出来,然后玩nim游戏,NiuNiu后手,问最多能选多少堆使得必胜?

 

题解

显然问题转换成n个数中选最多的数出来使得异或和为0。

设:

dp[i][j]为现在已经丢弃了i个,这些数的异或和能否为j。

sum为n堆石头的异或和。

根据线性基的原理我们大概知道,n个数的异或和大概可以等价于用log(a)个数来表示,a是n个数中最大的数。

ai现在最大为5e5, log(a[i])大概是19。也就是说,丢弃19个数的时候必定可以找到异或和为sum的那些数。

dp[i][j]中i的上限为19。这个dp是19 * n * n的。

我们发现可以用fwt来优化dp。

d[i][j] = d[i - 1][k] * a[l] (k ^ l = j)  (a[]数组为初始时有无j这个数)。

最多19次即可找到答案。

复杂度n log^2(n)

#include <bits/stdc++.h>
#define debug(x) cout<<#x<<" = "<<(x)<<endl;
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;

const int INF = 0x3f3f3f3f;
const ll INFLL = 0x3f3f3f3f3f3f3f3fll;
const int MX = 1e6 + 5;
const int MAXN = 1e6 + 5;

ll a[MAXN], b[MAXN];

void fwt(ll f[], int mx, int op) {
    int n = (1 << mx);
    for (int i = 1; i <= mx; ++i) {
        int m = (1 << i), len = m >> 1;
        for (int r = 0; r < n; r += m) {
            int t1 = r, t2 = r + len;
            for (int j = 0; j < len; ++j, ++t1, ++t2) {
                ll x1 = f[t1], x2 = f[t2];
                if (op == 1) {   //xor
                    f[t1] = x1 + x2;
                    f[t2] = x1 - x2;
                    //if(f[t1] >= mod) f[t1] -= mod;
                    //if(f[t2] < 0) f[t2] += mod;
                }
                if (op == 2) {   //and
                    f[t1] = x1 + x2;
                    f[t2] = x2;
                    //if(f[t1] >= mod) f[t1] -= mod;
                }
                if (op == 3) {   //or
                    f[t1] = x1;
                    f[t2] = x2 + x1;
                    //if(f[t2] >= mod) f[t2] -= mod;
                }
            }
        }
    }
}
void ifwt(ll f[], int mx, int op) {
    int n = (1 << mx);
    for (int i = mx; i >= 1; --i) {
        int m = (1 << i), len = m >> 1;
        for (int r = 0; r < n; r += m) {
            int t1 = r, t2 = r + len;
            for (int j = 0; j < len; ++j, ++t1, ++t2) {
                ll x1 = f[t1], x2 = f[t2];
                if (op == 1) {   //xor
                    f[t1] = (x1 + x2) / 2;
                    f[t2] = (x1 - x2) / 2;
                    //f[t1] = (x1 + x2) * inv2;
                    //f[t2] = (x1 - x2) * inv2;
                    //if(f[t1] >= mod) f[t1] %= mod;
                    //if(f[t2] >= mod) f[t2] %= mod;
                    //if(f[t2] < 0) f[t2] = f[t2] % mod + mod;
                }
                if (op == 2) {   //and
                    f[t1] = x1 - x2;
                    f[t2] = x2;
                    //if(f[t1] < 0) f[t1] += mod;
                }
                if (op == 3) {   //or
                    f[t1] = x1;
                    f[t2] = x2 - x1;
                    //if(f[t2] < 0) f[t2] += mod;
                }
            }
        }
    }
}
void gao(ll a[], ll b[], int mx) {
    int n = 1 << mx;
    fwt(a, mx, 1);
    for (int i = 0; i < n; i++) a[i] = a[i] * b[i];
    ifwt(a, mx, 1);
}


int main() {
#ifdef LOCAL
    freopen("input.txt", "r", stdin);
#endif
    int n;
    cin >> n;
    int sum = 0;
    for(int i = 1; i <= n; i++) {
        int x;
        scanf("%d", &x);
        b[x] = a[x] = 1;
        sum ^= x;
    }
    b[0] = a[0] = 1;
    if(sum == 0) return 0 * printf("%d\n", n);
    fwt(b, 19, 1);
    while(!a[sum]) {
        n--;
        gao(a, b, 19);
        for(int i = 0; i < (1<<19); i++) a[i] = min(a[i], 1LL);
    }
    printf("%d\n", n - 1);
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值