集合幂级数与 FWT
集合幂级数与 FWT
集合幂级数其实和多项式很像。具体来说:
没错,“集合幂”就是字面意思。幂上面是个集合。
举个更形象的例子:
平时我们可以直接把它当作一个长度为
定义了集合幂级数就可以定义其乘法卷积。比如我们定义
其中
通过
我们的思路是,对集合幂级数
- 通过
a 可以变换得到fwt(a) ; - 通过
fwt(a) 可以逆变换得到a ; -
只要能构造,问题就很简单了。先
考虑神奇的构造。
或
即高维前缀和。同样的,逆变换就是高维差分。
::::success[为什么对?]
::::
虽然说咱有经典高维前缀和写法,但为了和后面的兼容,我们这样写:
void fwt(int *a, int o) { // o=1 正变换,o=-1 逆变换
for (int i = 1; i < 1 << n; i <<= 1)
for (int j = 0; j < 1 << n; j += i + i)
for (int k = 0; k < i; ++ k ) {
a[i + j + k] += o * a[j + k];
}
}
具体来说,我们枚举每个长度为
与
和或是反着的。高维前缀和变成高维后缀和。代码:
void fwt(int *a, int o) { // o=1 正变换,o=-1 逆变换
for (int i = 1; i < 1 << n; i <<= 1)
for (int j = 0; j < 1 << n; j += i + i)
for (int k = 0; k < i; ++ k ) {
a[j + k] += o * a[i + j + k];
}
}
异或
这才是重头戏。
::::success[为什么对?]
考虑
即,
::::
考虑求
这是为什么呢?即我考虑求每个
正变换是这样的:
void fwt(int *a) {
for (int i = 1; i < 1 << n; i <<= 1)
for (int j = 0; j < 1 << n; j += i << 1)
for (int k = 0; k < i; ++ k ) {
int x = a[j + k], y = a[i + j + k];
a[j + k] = x + y;
a[i + j + k] = x - y;
}
}
逆变换的话,稍微推一推,
void fwt(int *a, int o) { // o=1 是正变换,o=inv2 是负变换
for (int i = 1; i < 1 << n; i <<= 1)
for (int j = 0; j < 1 << n; j += i << 1)
for (int k = 0; k < i; ++ k ) {
int x = a[j + k], y = a[i + j + k];
a[j + k] = x + y;
a[i + j + k] = x - y;
a[j + k] *= o;
a[i + j + k] *= o;
}
}
例题 1 CF2194F2
给定一棵包含
n 个顶点的树。每个顶点上写有一个非负整数a_v 。同时给定k 个互不相同的非负整数b_1 \dots b_k 。我们称一组边为美丽的,如果在移除这些边后,树被分成若干连通块,并且每个连通块内所有顶点的数a_v 的按位异或值属于集合b 。你需要计算该树中美丽的边集的数量,对10^9+7 取模。
先考虑 Easy Version。
暴力做法是设
复杂度
设
具体的,设
:::success[Code]
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, P = 1e9 + 7;
int n, a[N], b[4], k, sum[N];
vector<int> g[N];
void add(int &a, int b) {
a += b;
if (a >= P) a -= P;
}
int id(int x) {
for (int i = 0; i < k; ++ i )
if (b[i] == x) return i;
return -1;
}
int f[N][1 << 4], h[1 << 4], p[1 << 4];
void dfs(int u, int F) {
sum[u] = a[u];
for (int v : g[u])
if (v != F) {
dfs(v, u);
sum[u] ^= sum[v];
}
memset(f[u], 0, sizeof(int) * 16);
f[u][0] = 1;
for (int v : g[u])
if (v != F) {
for (int i = 0; i < 1 << k; ++ i ) h[i] = f[u][i], f[u][i] = 0;
for (int i = 0; i < 1 << k; ++ i )
for (int j = 0; j < 1 << k; ++ j ) {
add(f[u][i ^ j], 1ll * h[i] * f[v][j] % P);
int x = id(sum[v] ^ p[j]);
if (~x) add(f[u][i ^ j ^ (1 << x)], 1ll * h[i] * f[v][j] % P);
}
}
}
int solve() {
cin >> n >> k;
for (int i = 1; i <= n; ++ i ) g[i].clear();
for (int i = 1; i < n; ++ i ) {
int a, b;
cin >> a >> b;
g[a].push_back(b), g[b].push_back(a);
}
for (int i = 1; i <= n; ++ i ) cin >> a[i];
for (int i = 0; i < k; ++ i ) cin >> b[i];
for (int s = 0; s < 1 << k; ++ s ) {
p[s] = 0;
for (int i = 0; i < k; ++ i ) {
if (s >> i & 1) p[s] ^= b[i];
}
}
dfs(1, 0);
int res = 0;
for (int s = 0; s < 1 << 4; ++ s ) {
if (~id(sum[1] ^ p[s])) add(res, f[1][s]);
}
return res;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T;
cin >> T;
while (T -- ) cout << solve() << '\n';
return 0;
}
:::
然后考虑 Hard Version。
这个转移与 FWT 极像。不过在第二种转移里,
不过很可惜,一次 FWT 复杂度是
现在我们尝试直接维护
注意到第二种转移可以进行(即可以找到
那么现在要做的是对
第二个问题是容易的。考虑定义
那么
上面两次异或卷积,可以通过预处理
:::success[Code]
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, P = 1e9 + 7, inv2 = P + 1 >> 1;
int n, k, s[N], pos[10], b[10], w[N];
vector<int> adj[N], vec;
int a[32], ppc[N], inv2m, m;
void insert(int x) {
for (int i = 31; ~i; -- i ) {
if (x >> i & 1) {
if (a[i]) x ^= a[i];
else {
a[i] = x;
break;
}
}
}
}
int f[N][1 << 10];
int A[N], g[1 << 10], h[1 << 10];
void FWT(int *a) {
for (int i = 1; i < 1 << m; i <<= 1)
for (int j = 0; j < 1 << m; j += i + i)
for (int u = 0; u < i; ++ u ) {
const int x = a[j + u], y = a[j + u + i];
a[j + u] = x + y, a[j + u + i] = x - y;
if (a[j + u] >= P) a[j + u] -= P;
if (a[j + u + i] < 0) a[j + u + i] += P;
}
}
void IFWT(int *a) {
for (int i = 1; i < 1 << m; i <<= 1)
for (int j = 0; j < 1 << m; j += i + i)
for (int u = 0; u < i; ++ u ) {
const int x = a[j + u], y = a[j + u + i];
a[j + u] = 1ll * (x + y) * inv2 % P;
a[j + u + i] = 1ll * (x - y + P) * inv2 % P;
}
}
void dfs(int u, int F) {
for (int i = 0; i < 1 << m; ++ i ) {
f[u][i] = 1;
}
s[u] = w[u];
for (int v : adj[u])
if (v != F) {
dfs(v, u);
if (~A[v]) {
int sum = 0;
for (int s = 0; s < 1 << m; ++ s ) {
sum = (sum + 1ll * f[v][s] * (ppc[s & A[v]] ? P - h[s] : h[s])) % P;
}
sum = 1ll * sum * inv2m % P;
if (sum)
for (int s = 0; s < 1 << m; ++ s ) {
f[v][s] += ppc[s & A[v]] ? P - sum : sum;
if (f[v][s] >= P) f[v][s] -= P;
}
}
for (int s = 0; s < 1 << m; ++ s ) {
f[u][s] = 1ll * f[u][s] * f[v][s] % P;
}
s[u] ^= s[v];
}
int x = s[u];
A[u] = 0;
for (int i = 0; i < m; ++ i )
if (x >> vec[i] & 1) {
A[u] |= 1 << i;
x ^= a[vec[i]];
}
if (x) A[u] = -1;
}
int solve() {
cin >> n >> k;
for (int i = 1; i <= n; ++ i ) adj[i].clear();
for (int i = 1; i < n; ++ i ) {
int a, b;
cin >> a >> b;
adj[a].push_back(b);
adj[b].push_back(a);
}
for (int i = 1; i <= n; ++ i ) {
cin >> w[i];
}
memset(a, 0, sizeof a);
for (int i = 0; i < k; ++ i ) {
cin >> b[i];
insert(b[i]);
}
vec.clear();
for (int j = 31; ~j; -- j ) {
if (a[j]) vec.push_back(j);
}
m = vec.size();
inv2m = 1;
for (int i = 0; i < m; ++ i ) inv2m = 1ll * inv2m * inv2 % P;
memset(h, 0, sizeof h);
for (int i = 0; i < k; ++ i ) {
int res = 0;
for (int j = 0; j < m; ++ j ) {
if (b[i] >> vec[j] & 1) {
res |= 1 << j;
b[i] ^= a[vec[j]];
}
}
pos[i] = res;
h[pos[i]] = 1;
}
FWT(h);
dfs(1, 0);
IFWT(f[1]);
if (A[1] == -1) return 0;
int res = 0;
for (int i = 0; i < k; ++ i ) {
res = (res + f[1][A[1] ^ pos[i]]) % P;
}
return res;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
for (int i = 0; i < N; ++ i ) {
ppc[i] = __builtin_popcount(i) & 1;
}
int T;
cin >> T;
while (T -- ) cout << solve() << '\n';
return 0;
}
:::
例题 2 CF1119H
给定
x,y,z 。有n 个数组,第i 个数组用三元组(a_i,b_i,c_i) 描述,表示第i 个数组由x 个a_i ,y 个b_i ,z 个c_i 构成。对每个t=0\dots 2^k-1 ,求从每个数组中选出一个数,且选出数的异或和为t 的方案数。
构造
如果先把所有
由于
考虑一个固定的
考虑构造四个方程把他们求出来。
首先显然有一个
如果构造