Processing math: 100%

题目描述

你有一个正整数集合 S,现在请你回答对于 1kn,有多少种将编号为 1k 的球放入一些盒子的方案,使得每个盒子里球的数量都属于 S。你只想知道答案的奇偶性。

注意:球可以区分,盒子不可以区分。

输入格式

共一行,包含一个长度为 n0/1 串 (1n2×106),其中第 i 位为 1 当且仅当 iS

输出格式

输出一行,包含一个长度为 n0/1 串,其中第 i 位表示 k=i 时的答案模 2 的值。

题解

对于基础的球和盒子的问题,考虑建立生成函数模型。根据题意,这是一个有标号无序组

记指数生成函数 f(x)=iSxii!,则答案的指数生成函数即为 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 行)。