你有一个正整数集合 S,现在请你回答对于 1≤k≤n,有多少种将编号为 1∼k 的球放入一些盒子的方案,使得每个盒子里球的数量都属于 S。你只想知道答案的奇偶性。
注意:球可以区分,盒子不可以区分。
共一行,包含一个长度为 n 的 0/1 串 (1≤n≤2×106),其中第 i 位为 1 当且仅当 i∈S。
输出一行,包含一个长度为 n 的 0/1 串,其中第 i 位表示 k=i 时的答案模 2 的值。
双 倍 经 验(大雾
对于基础的球和盒子的问题,考虑建立生成函数模型。根据题意,这是一个有标号无序组,
记指数生成函数 f(x)=∑i∈Sxii!,则答案的指数生成函数即为 g(x)=expf(x)。
不过这里的问题是整个运算需要在模 2 下进行,也就是说这些阶乘分母可能不存在。
也就是说数域为 F2。根据 [soj1006]Nimber Exp 的经验,在特征为 2 的域上做多项式 exp,可以通过对分子进行子集卷积得到二项卷积,而一个常数项为 1 的生成函数的二项卷积逆为其本身。
因此可以像那道题一样进行 Newton 迭代,在 O(nlog2n) 时间内得到答案。
不过,这道题由于数域仅为 F2 而不是 F232,因此可以使用类似压位的思想来 "去掉一个 log",从而才能跑 221。
事实上,在子集卷积的时候,我们可以将占位多项式压缩到一个 32 位整数中存储,然后两个 32 位整数的 "多项式乘法" 显然可以在 O(logn) 时间内完成 (可以参考这里)。
同理,在记录原多项式的时候,也可以使用适当的压位。
于是总时间复杂度即为 O(nlogn),或更严谨地,O(nlog2nω)。
#include <bits/stdc++.h>
#define popc __builtin_popcount
#define parity __builtin_parity
#define lg2 std::__lg
#define ctz __builtin_ctz
using std::cin;
using std::cout;
typedef unsigned short u16;
typedef unsigned int u32;
const int N = 65540;
typedef u32 vec[N], *pvec;
namespace bin {
const u32 mask[32] = {
0x00000001u, 0x00000003u, 0x00000005u, 0x0000000fu,
0x00000011u, 0x00000033u, 0x00000055u, 0x000000ffu,
0x00000101u, 0x00000303u, 0x00000505u, 0x00000f0fu,
0x00001111u, 0x00003333u, 0x00005555u, 0x0000ffffu,
0x00010001u, 0x00030003u, 0x00050005u, 0x000f000fu,
0x00110011u, 0x00330033u, 0x00550055u, 0x00ff00ffu,
0x01010101u, 0x03030303u, 0x05050505u, 0x0f0f0f0fu,
0x11111111u, 0x33333333u, 0x55555555u, 0xffffffffu
};
u16 revi[65536];
void init() {for (int i = 1; i < 65536; ++i) revi[i] = revi[i >> 1] >> 1 | (i & 1) << 15;}
inline u32 rev(u32 x) {return revi[x >> 16] | revi[x & 65535u] << 16;}
}
namespace poly {
int l, n;
vec B1;
u32 M1[N << 5], M2[N << 5];
u32 exp_unit(u32 x) {
assert(!(x & 1)), x = bin::rev(x);
int i; u32 y = 1;
for (i = 1; i < 32; ++i) y |= (u32)parity(x >> (31 - i) & y & bin::mask[i - 1]) << i;
return y;
}
u32 conv_unit(u32 x, u32 y) {
u32 z = 0;
for (; x; x &= x - 1) z ^= y << ctz(x);
return z;
}
inline void FMT_init(int len) {n = 1 << (l = len);}
void FMT(pvec a) {
int i, len = 1; u32 *j, *k;
for (i = 0; i < l; ++i, len <<= 1)
for (j = a; j != a + n; j += len << 1)
for (k = j; k != j + len; ++k) k[len] ^= *k;
}
void FMT(pvec a, pvec b) {
for (int i = 0; i < n; ++i) b[i] = (a[i >> 5] >> (i & 31) & 1) << popc(i);
FMT(b);
}
void IFMT(pvec a, pvec b) {
FMT(a);
for (int i = 0; i < n; ++i) b[i >> 5] |= (a[i] >> popc(i) & 1) << (i & 31);
}
void exp(int deg, pvec a, pvec b) {
int i, len, n;
if (*b = exp_unit(*a), deg < 2) return;
memset(b + 2, 0, i = 8 << lg2(deg - 1)), memset(B1, 0, i);
for (len = 0, n = 1; n < deg; ++len, n <<= 1) {
FMT_init(len + 5);
memcpy(B1, a + n, n << 2), *B1 ^= (len ? b[n >> 1] : *b >> 16) & 1;
FMT(B1, M1), FMT(b, M2);
for (i = 0; i < n << 5; ++i) M1[i] = conv_unit(M1[i], M2[i]);
IFMT(M1, b + n);
}
}
}
int n;
vec f, g;
char s[N << 5];
int main() {
int i, L; bin::init();
cin >> (s + 1), L = strlen(s + 1), n = L >> 5;
for (i = 1; i <= L; ++i) f[i >> 5] |= u32(s[i] & 1) << (i & 31);
poly::exp(n + 1, f, g);
for (i = 1; i <= L; ++i) s[i] = (g[i >> 5] >> (i & 31) & 1) | 48;
cout << s + 1 << '\n';
return 0;
}
坑1:由于我们对多项式进行了压位,因此 Newton 迭代初始时可以直接预处理 32 项,在这一部分可以通过递推公式及掩码快速与处理。
坑2:注意两个压位多项式的 "卷积" 需要用到的操作是异或 (^
),而不是或 (|
) 等 (第 47 行)。