Forever Young

洛谷 P5970 [POI2016]Nim z utrudnieniem

题意

\(\texttt{Nim}\)游戏,一共\(m\)颗石子分成了\(n\)堆,每堆石子数为\(a_i\),后手可以在游戏开始前扔掉\(d\)的倍数堆石子,但不能扔掉所有石子,求后手获胜方式有多少种。

\(d\leq10,n\leq5\times10^5,a_i\leq10^6\)\(m\)不直接给出但保证\(m\leq10^7\)

思路

\(\texttt{Nim}\)游戏后手必胜条件:每堆石子的个数异或和为\(0\)

\(f[i][j][k]\)表示到第\(i\)个数,前\(i\)个数中选出的数的个数\(\text{mod}\ d=j\),异或和为\(k\)的方案数,每次枚举当前堆是否扔掉。

但是时间复杂度是\(O(nmd)\)的,显然不能过……

优化需要注意到异或具有的性质:任意个\(\leq x\)的数的异或和\(\leq2x\)。具体来讲就是说任意长度为\(k\)的数的异或和小于任意长度为\(k+1\)的数

因此可以考虑将所有堆的石子数从小到大排序,每次新加入一个数,数的上界一定不会大于\(2\times a_i\)

所以这样时间复杂度就变成了\(O(\sum\limits_{i=1}^na_id)=O(md)\)

但是空间超了,但是只有\(k\bigoplus a_i\)\(k\)之间可以相互转移,所以可以开一个小空间\(t\),原地完成转移

还有就是\(n\)\(d\)的倍数时会算上全选的情况,此时答案需要减\(1\)

常数很大,记得开\(O2\)

代码

/*
Author:Loceaner
DP+NIM游戏+状态优化 
*/
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int A = 5e5 + 11;
const int B = (1 << 20) + 11;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;

inline int read() {
  char c = getchar(); int x = 0, f = 1;
  for ( ; !isdigit(c); c = getchar()) if (c == '-') f = -1;
  for ( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
  return x * f;
}

int n, d, sum, a[A], t[B], f[10][B], m = 1;

int main() {
  n = read(), d = read();
  for (int i = 1; i <= n; i++) a[i] = read(), sum ^= a[i];
  sort(a + 1, a + 1 + n);
  f[0][0] = 1;
  for (int i = 1; i <= n; i++) {
    while (m <= a[i]) m <<= 1;
    for (int j = 0; j < m; j++) t[j] = f[d - 1][j];
    for (int k = d - 1; k; k--) 
      for (int j = 0; j < m; j++)
        f[k][j] = (f[k][j] + f[k - 1][j ^ a[i]]) % mod;
    for (int j = 0; j < m; j++) f[0][j] = (f[0][j] + t[j ^ a[i]]) % mod;
  }
  cout << (f[0][sum] - (n % d == 0) + mod) % mod << '\n';
  return 0;
}
posted @ 2020-07-07 07:12  Loceaner  阅读(182)  评论(4编辑  收藏  举报