FWT学习笔记
FWT
快速沃尔什变换,用来解决位运算相关的卷积。常见的有与、或、异或三种。
P4717 【模板】快速莫比乌斯/沃尔什变换 (FMT/FWT)#
给定长度为
两个序列 ,设 分别当
是 or,and,xor 时求出 。
。
考虑构造一种可逆的线性变换,记
其中
因为变换是线性的,所以
然后考虑如何构造这个变换。
or#
设现在数列长度为
显然
因为有
所以有
既然正变换是
void FWT_OR(int* a, int v) {
for (int k = 2; k <= (1 << n); k <<= 1)
for (int len = k >> 1, i = 0; i < (1 << n); i += k)
for (int j = i; j < i + len; j++) (a[j + len] += (a[j] * v + mod) % mod) %= mod;
}
当 v = 1
时是正变换,v = -1
时是逆变换。
事实上,观察上面的代码(或者手玩几组数据)可以发现最终的
可以看出来 or 卷积是高维前缀和。
and#
和 or 操作类似的,构造
容易发现
可以看出来 and 卷积实际上是高维后缀和。
void FWT_AND(int* a, int v) {
for (int k = 2; k <= (1 << n); k <<= 1)
for (int len = k >> 1, i = 0; i < (1 << n); i += k)
for (int j = i; j < i + len; j++) (a[j] += (a[j + len] * v + mod) % mod) %= mod;
}
xor#
因为有
而
根据式子,可以得到变换
显然其逆变换是
看起来挺像 FFT 的,事实上 xor 卷积是高维循环卷积。
void FWT_XOR(int* a, int v) {
for (int k = 2; k <= (1 << n); k <<= 1)
for (int len = k >> 1, i = 0; i < (1 << n); i += k)
for (int j = i; j < i + len; j++) {
int x = a[j], y = a[j + len];
a[j] = 1ll * (x + y) * v % mod;
a[j + len] = 1ll * (x - y + mod) * v % mod;
}
}
Code of P4717
#include<cstdio>
const int M = 2e5 + 10;
const int mod = 998244353;
int n, a[M], b[M], c[M];
void FWT_OR(int* a, int v) {
for (int k = 2; k <= (1 << n); k <<= 1)
for (int len = k >> 1, i = 0; i < (1 << n); i += k)
for (int j = i; j < i + len; j++) (a[j + len] += (a[j] * v + mod) % mod) %= mod;
}
void FWT_AND(int* a, int v) {
for (int k = 2; k <= (1 << n); k <<= 1)
for (int len = k >> 1, i = 0; i < (1 << n); i += k)
for (int j = i; j < i + len; j++) (a[j] += (a[j + len] * v + mod) % mod) %= mod;
}
void FWT_XOR(int* a, int v) {
for (int k = 2; k <= (1 << n); k <<= 1)
for (int len = k >> 1, i = 0; i < (1 << n); i += k)
for (int j = i; j < i + len; j++) {
int x = a[j], y = a[j + len];
a[j] = 1ll * (x + y) * v % mod;
a[j + len] = 1ll * (x - y + mod) * v % mod;
}
}
int main() {
scanf("%d", &n);
for (int i = 0; i < (1 << n); i++) scanf("%d", &a[i]);
for (int i = 0; i < (1 << n); i++) scanf("%d", &b[i]);
FWT_OR(a, 1), FWT_OR(b, 1);
for (int i = 0; i < (1 << n); i++) c[i] = 1ll * a[i] * b[i] % mod;
FWT_OR(c, -1), FWT_OR(a, -1), FWT_OR(b, -1);
for (int i = 0; i < (1 << n); i++) printf("%d ", c[i]);
putchar('\n');
FWT_AND(a, 1), FWT_AND(b, 1);
for (int i = 0; i < (1 << n); i++) c[i] = 1ll * a[i] * b[i] % mod;
FWT_AND(c, -1), FWT_AND(a, -1), FWT_AND(b, -1);
for (int i = 0; i < (1 << n); i++) printf("%d ", c[i]);
putchar('\n');
FWT_XOR(a, 1), FWT_XOR(b, 1);
for (int i = 0; i < (1 << n); i++) c[i] = 1ll * a[i] * b[i] % mod;
FWT_XOR(c, (mod + 1) >> 1);
for (int i = 0; i < (1 << n); i++) printf("%d ", c[i]);
return 0;
}
CF662C Binary Table#
有一个
行 列的表格,每个元素都是 。 每次操作可以选择一行或一列,把
翻转,即把 换为 ,把 换为 。 请问经过若干次操作后,表格中最少有多少个
。
。
每行只有翻转和不翻转之分。考虑翻转的行的集合为
Code of CF662C
#include<cstdio>
#include<algorithm>
const long long M = 2e6 + 10;
long long A[M], B[M], C[M];
int n, m, a[21][100010];
void FWT_XOR(long long* a, int op) {
for (int k = 2; k <= (1 << n); k <<= 1)
for (int len = k >> 1, i = 0; i < (1 << n); i += k)
for (int j = i; j < i + len; j++) {
long long x = a[j], y = a[j + len];
if (op == 1)
a[j] = x + y, a[j + len] = x - y;
if (op == -1)
a[j] = (x + y) / 2, a[j + len] = (x - y) / 2;
}
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++) scanf("%1d", &a[i][j]);
for (int j = 0; j < m; j++) {
int S = 0;
for (int i = 0; i < n; i++) S += a[i][j] * (1 << i);
A[S]++;
}
for (int i = 1; i < (1 << n); i++) B[i] = B[i - (i & -i)] + 1;
for (int i = 0; i < (1 << n); i++) B[i] = std::min(B[i], n - B[i]);
FWT_XOR(A, 1), FWT_XOR(B, 1);
for (int i = 0; i < (1 << n); i++) C[i] = A[i] * B[i];
FWT_XOR(C, -1);
long long ans = 10000000000007;
for (int i = 0; i < (1 << n); i++) ans = std::min(ans, C[i]);
printf("%lld\n", ans);
return 0;
}
HDU5823 Color II#
给定一张无向图,设点集
至少要 种颜色染色,能够使得其中有边相连的两点不同颜色。求 。
。
设
先预处理出
Code of HDU5823
#pragma GCC optimize("Ofast")
#include <cstdio>
#include <cstring>
const int N = 1 << 18, M = 20;
typedef unsigned int uint;
int T, n;
bool G[M][M], vis[N];
uint f[M][N], pw233[N];
void FWT_OR(uint* a, int op) {
for (int k = 2; k <= (1 << n); k <<= 1)
for (int len = k >> 1, i = 0; i < (1 << n); i += k)
for (int j = i; j < i + len; j++) a[j + len] += a[j] * op;
}
inline void clear();
int main() {
pw233[0] = 1;
for (int i = 1; i < N; i++) pw233[i] = pw233[i - 1] * 233;
scanf("%d", &T);
while (T--) {
clear();
scanf("%d", &n);
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++) scanf("%1d", &G[i][j]);
for (int S = 1; S < (1 << n); S++) {
bool flag = true;
for (int i = 0; i < n && flag; i++)
for (int j = 0; j < n && flag; j++)
if ((S & (1 << i)) && (S & (1 << j)) && G[i][j]) flag = false;
if (flag) f[1][S] = 1;
}
uint ans = 0;
for (int i = 1; i < n; i++) {
for (int S = 1; S < (1 << n); S++)
if (f[i][S] && !vis[S]) vis[S] = true, ans += i * pw233[S];
FWT_OR(f[i], 1);
for (int S = 0; S < (1 << n); S++) f[i + 1][S] = f[1][S] * f[i][S];
FWT_OR(f[i + 1], -1);
}
for (int S = 1; S < (1 << n); S++)
if (f[n][S] && !vis[S]) ans += n * pw233[S];
printf("%u\n", ans);
}
return 0;
}
inline void clear() {
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++) G[i][j] = 0;
for (int i = 0; i < n; i++)
for (int j = 0; j < (1 << n); j++) f[i][j] = 0;
for (int i = 0; i < (1 << n); i++) vis[i] = false;
}
UOJ310 黎明前的巧克力#
长为
的序列 ,选出两个互不相交的集合,满足这两个集合内元素异或和相同且不同时为空,问方案数。
。
两个集合元素异或和相同,说明将选出的所有元素异或起来和为
设
显然有转移
FWT 优化转移,构造向量
但是这样要做
考虑异或卷积的变换:
但是
故
考虑如何快速求得
于是
其实直接对
答案是
Code of UOJ310
#include <cstdio>
#include <cassert>
#include <cstring>
#include <algorithm>
#define int long long
const int N = 1 << 20, M = N + 10;
const int mod = 998244353;
int n, a[M], f[M], g[M], h[M], pw3[M];
void FWT_XOR(int* a, int v, bool flag = true) {
for (int k = 2; k <= N; k <<= 1)
for (int l = k >> 1, i = 0; i < N; i += k)
for (int j = i; j < i + l; j++) {
int x = a[j], y = a[j + l];
a[j] = 1ll * (x + y) * v;
a[j + l] = 1ll * (x - y) * v;
if (flag) ((a[j] %= mod) += mod) %= mod, ((a[j + l] %= mod) += mod) %= mod;
}
}
signed main() {
pw3[0] = 1;
for (int i = 1; i < M; i++) pw3[i] = 3ll * pw3[i - 1] % mod;
scanf("%lld", &n);
for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);
for (int i = 1; i <= n; i++) h[0]++, h[a[i]] += 2;
FWT_XOR(h, 1, false);
int mn = 0, mx = 0;
for (int i = 0; i < N; i++) mn = std::min(mn, h[i]), mx = std::max(mx, h[i]);
for (int i = 0; i < N; i++) {
int _3 = (h[i] + n) / 4;
h[i] = ((n - _3) & 1) ? (mod - pw3[_3]) : pw3[_3];
}
FWT_XOR(h, (mod + 1) >> 1, true);
printf("%d\n", (h[0] + mod - 1) % mod);
return 0;
}
其他的题还有 BZOJ4589 Hard Nim | sol
其他位运算#
FWT 不止可以应用于 or, and, xor 卷积。
FWT 可以看做一个向量
所以如果矩阵
例如 or 卷积,
HDU6966 I love sequences#
给定三个长为
的序列 。 设
,其中 。 代表三进制下的按位 。 求
。
。
枚举
虽然是三进制,但是还是位运算卷积,考虑 FWT。
列出关于变换矩阵
容易解得
其实可以交换任意两行,仍然满足转移矩阵的性质。
故
对于每个
Code of HDU6966
#pragma GCC optimize("Ofast")
#include <cstdio>
#define int long long
const int mod = 1e9 + 7;
const int M = 6e5 + 10;
int n, a[M], b[M], c[M], d[M], a0[M], b0[M];
int _lg3[M], pw3[M];
inline int read() {
char ch = getchar();
int x = 0;
while (ch < '0' || ch > '9') ch = getchar();
while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
return x;
}
inline void FWT(int* a, int op, int L) {
for (int k = 3; k <= L; k *= 3)
for (int len = k / 3, i = 0; i < L; i += k)
for (int j = i; j < i + len; j++) {
int x = a[j], y = a[j + len], z = a[j + 2 * len];
if (op == 1) {
a[j] = x, a[j + len] = (x + z) % mod, a[j + 2 * len] = (x + y + z) % mod;
} else {
a[j] = x, a[j + len] = (z - y + mod) % mod, a[j + 2 * len] = (y - x + mod) % mod;
}
}
}
signed main() {
_lg3[0] = -1, pw3[0] = 1;
for (int i = 1; i < M; i++) _lg3[i] = _lg3[i / 3] + 1;
for (int i = 1; i <= 20; i++) pw3[i] = pw3[i - 1] * 3;
n = read();
for (int i = 1; i <= n; i++) a[i] = read();
for (int i = 1; i <= n; i++) b[i] = read();
for (int i = 1; i <= n; i++) c[i] = read();
int ans = 0;
for (int p = 1; p <= n; p++) {
int L = 1;
while (L <= n / p) L *= 3;
//int L = pw3[_lg3[n / p] + 1];
for (int i = 1; i <= n / p; i++) a0[i] = a[i], b0[i] = b[i];
FWT(a0, 1, L), FWT(b0, 1, L);
for (int i = 0; i < L; i++) d[i] = 1ll * a0[i] * b0[i] % mod;
FWT(d, -1, L);
//for (int i = 0; i < L; i++) (ans += 1ll * qpow(c[p], i) * d[i] % mod) %= mod;
int pwc = 1;
for (int i = 0; i < L; i++)
(ans += 1ll * pwc * d[i] % mod) %= mod, pwc = 1ll * pwc * c[p] % mod;
for (int i = 0; i < L; i++) d[i] = a0[i] = b0[i] = 0;
}
printf("%lld\n", ans);
return 0;
}
子集卷积
P6097 【模板】子集卷积#
给定两个长度为
的序列 ,求出序列 :
。
只有
注意到这两个限制相当于
设
暴力枚举
Code of P6097
#include <cstdio>
const int M = 21, N = 1 << 20;
const int mod = 1e9 + 9;
int n, a[M][N], b[M][N], c[M][N];
int pcnt[N];
inline void FWT_OR(int* a, int op) {
for (int k = 2; k <= (1 << n); k <<= 1)
for (int l = k >> 1, i = 0; i < (1 << n); i += k)
for (int j = i; j < i + l; j++) (((a[j + l] += 1ll * op * a[j] % mod) %= mod) += mod) %= mod;
}
int main() {
for (int i = 1; i < N; i++)
pcnt[i] = pcnt[i - (i & -i)] + 1;
scanf("%d", &n);
for (int i = 0; i < (1 << n); i++) scanf("%d", &a[pcnt[i]][i]);
for (int i = 0; i < (1 << n); i++) scanf("%d", &b[pcnt[i]][i]);
for (int i = 0; i <= n; i++) FWT_OR(a[i], 1), FWT_OR(b[i], 1);
for (int i = 0; i <= n; i++)
for (int j = 0; i + j <= n; j++)
for (int S = 0; S < (1 << n); S++)
(c[i + j][S] += 1ll * a[i][S] * b[j][S] % mod) %= mod;
for (int i = 0; i <= n; i++) FWT_OR(c[i], -1);
for (int i = 0; i < (1 << n); i++) printf("%d ", c[pcnt[i]][i]);
return 0;
}
作者:zzxLLL
出处:https://www.cnblogs.com/zzxLLL/p/17810165.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话