洛谷 P5970 [POI2016]Nim z utrudnieniem

题意

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

(dleq10,nleq5 imes10^5,a_ileq10^6)(m)不直接给出但保证(mleq10^7)

思路

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

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

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

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

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

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

但是空间超了,但是只有(kigoplus 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 << '
';
  return 0;
}
原文地址:https://www.cnblogs.com/loceaner/p/13257899.html