# P4717 【模板】快速莫比乌斯 / 沃尔什变换 (FMT/FWT)
先放一个模板后面写题直接复制就可以了。
原理就不解释了,大家自己去看题解吧 QwQ
这里直接放代码。
#include <bits/stdc++.h> | |
#define ll long long | |
using namespace std; | |
namespace IO{ | |
inline int read(){ | |
int x = 0; | |
char ch = getchar(); | |
while(!isdigit(ch)) ch = getchar(); | |
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar(); | |
return x; | |
} | |
template <typename T> inline void write(T x){ | |
if(x > 9) write(x / 10); | |
putchar(x % 10 + '0'); | |
} | |
} | |
using namespace IO; | |
const int N = 1 << 18; | |
const int mod = 998244353; | |
int n; | |
int A[N], B[N], a[N], b[N]; | |
int f[N]; | |
inline int add(int x) {return x >= mod ? x % mod : x;} | |
inline int sub(int x) {return x < 0 ? x % mod + mod : x;} | |
inline int calc(ll x) {return (x % mod + mod) % mod;} | |
inline void init(){ | |
memcpy(a, A, sizeof(A)), memcpy(b, B, sizeof(B)); | |
} | |
inline void OR(int f[], int typ){ | |
for(int k = 1; k < n; k <<= 1) | |
for(int i = 0; i < n; i += (k << 1)) | |
for(int j = 0; j < k; ++j) | |
f[i + j + k] = calc(f[i + j + k] + f[i + j] * typ); | |
} | |
inline void AND(int f[], int typ){ | |
for(int k = 1; k < n; k <<= 1) | |
for(int i = 0; i < n; i += (k << 1)) | |
for(int j = 0; j < k; ++j) | |
f[i + j] = calc(f[i + j] + f[i + j + k] * typ); | |
} | |
inline void XOR(int f[], int typ){ | |
for(int k = 1; k < n; k <<= 1) | |
for(int i = 0; i < n; i += (k << 1)) | |
for(int j = 0; j < k; ++j){ | |
f[i + j] = add(f[i + j] + f[i + j + k]); | |
f[i + j + k] = sub(f[i + j] - f[i + j + k] - f[i + j + k]); | |
f[i + j] = calc(1ll * f[i + j] * typ), f[i + j + k] = calc(1ll * f[i + j + k] * typ); | |
} | |
} | |
inline void fwt_OR(){ | |
init(), OR(a, 1), OR(b, 1); | |
for(int i = 0; i < n; ++i) a[i] = 1ll * a[i] * b[i] % mod; | |
OR(a, -1); | |
for(int i = 0; i < n; ++i) write(a[i]), putchar(' '); | |
puts(""); | |
} | |
inline void fwt_AND(){ | |
init(), AND(a, 1), AND(b, 1); | |
for(int i = 0; i < n; ++i) a[i] = 1ll * a[i] * b[i] % mod; | |
AND(a, -1); | |
for(int i = 0; i < n; ++i) write(a[i]), putchar(' '); | |
puts(""); | |
} | |
inline void fwt_XOR(){ | |
init(), XOR(a, 1), XOR(b, 1); | |
for(int i = 0; i < n; ++i) a[i] = 1ll * a[i] * b[i] % mod; | |
XOR(a, 499122177); | |
for(int i = 0; i < n; ++i) write(a[i]), putchar(' '); | |
puts(""); | |
} | |
signed main(){ | |
n = 1 << read(); | |
for(int i = 0; i < n; ++i) A[i] = read(); | |
for(int i = 0; i < n; ++i) B[i] = read(); | |
fwt_OR(), fwt_AND(), fwt_XOR(); | |
return 0; | |
} |
# P6097【模板】子集卷积
经典应用,也有许多题目使用到了【子集卷积】这个做法。
我们把两个限制分开来考虑。
第一个 比较简单,直接使用 计算子集并卷积即可。
那么 如何处理呢?同时考虑这两个限制,相当于是 ,所以我们再开一维数组记录 中 1 的个数, 中 表示 中 1 的个数,那么有:
\begin{equation} f_{i, j}= \begin{cases} 0 & |j| \neq i\\ a_j & |j| = i \end{cases} \end{equation} \begin{equation} g_{i, j}= \begin{cases} 0 & |j| \neq i\\ b_j & |j| = i \end{cases} \end{equation}即为 和 的并卷积。
#include <bits/stdc++.h> | |
using namespace std; | |
namespace IO{ | |
inline int read(){ | |
int x = 0; | |
char ch = getchar(); | |
while(!isdigit(ch)) ch = getchar(); | |
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar(); | |
return x; | |
} | |
template <typename T> inline void write(T x){ | |
if(x > 9) write(x / 10); | |
putchar(x % 10 + '0'); | |
} | |
} | |
using namespace IO; | |
const int N = (1 << 20) + 10; | |
const int mod = 1e9 + 9; | |
int n, m; | |
int a[25][N], b[25][N], f[25][N]; | |
inline int calc(int x) {return __builtin_popcount(x);} | |
inline int add(int x) {return (x % mod + mod) % mod;} | |
inline void fwt_or(int f[], int typ){ | |
for(int k = 1; k < n; k <<= 1) | |
for(int i = 0; i < n; i += (k << 1)) | |
for(int j = 0; j < k; ++j) | |
f[i + j + k] = add(f[i + j + k] + 1ll * f[i + j] * typ); | |
} | |
signed main(){ | |
n = 1 << (m = read()); | |
for(int i = 0; i < n; ++i) a[calc(i)][i] = read(); | |
for(int i = 0; i < n; ++i) b[calc(i)][i] = read(); | |
for(int i = 0; i <= m; ++i) fwt_or(a[i], 1), fwt_or(b[i], 1); | |
for(int i = 0; i <= m; ++i) | |
for(int j = 0; j <= i; ++j) | |
for(int k = 0; k < n; ++k) | |
f[i][k] = add(f[i][k] + 1ll * a[j][k] * b[i - j][k] % mod); | |
for(int i = 0; i <= m; ++i) fwt_or(f[i], -1); | |
for(int i = 0; i < n; ++i) write(f[calc(i)][i]), putchar(' '); | |
return 0; | |
} |
# SP2829 TLE - Time Limit Exceeded
观察到 的取值范围为 , 也很小,显然要使用状压 。
设 表示第 位填 的方案数,那么有:
\begin{equation} dp_{i, j}= \begin{cases} 0& j\ \%\ c_i\ \neq 0\\ \sum\limits_{k \& j = 0}dp_{i - 1, j} & j\ \%\ c_i\ = 0 \end{cases} \end{equation}不难发现, 的所有取值就是 的所有子集,所以我们求一遍子集前缀和就可以转移了。
可以使用 来快速计算。
#include <bits/stdc++.h> | |
using namespace std; | |
namespace IO{ | |
inline int read(){ | |
int x = 0; | |
char ch = getchar(); | |
while(!isdigit(ch)) ch = getchar(); | |
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar(); | |
return x; | |
} | |
template <typename T> inline void write(T x){ | |
if(x > 9) write(x / 10); | |
putchar(x % 10 + '0'); | |
} | |
} | |
using namespace IO; | |
const int N = 55; | |
const int mod = 1e9; | |
int T, n, m; | |
int a[N], f[N][(1 << 15) + 10]; | |
inline int add(int x) {return x >= mod ? x - mod : x;} | |
inline void fwt_OR(int f[]){ | |
for(int k = 1; k < m; k <<= 1) | |
for(int i = 0; i < m; i += (k << 1)) | |
for(int j = 0; j < k; ++j) | |
f[i + j + k] = add(f[i + j + k] + f[i + j]); | |
} | |
signed main(){ | |
T = read(); | |
while(T--){ | |
memset(f, 0, sizeof(f)); | |
n = read(), m = 1 << read(); | |
for(int i = 1; i <= n; ++i) a[i] = read(); | |
for(int i = 1; i < m; ++i) f[1][i] = (i % a[1]) ? 1 : 0; | |
fwt_OR(f[1]); | |
for(int i = 2; i <= n; ++i){ | |
for(int j = 1; j < m; ++j) | |
if(j % a[i]) f[i][j] += f[i - 1][j ^ (m - 1)]; | |
fwt_OR(f[i]); | |
} | |
write(f[n][m - 1]), puts(""); | |
} | |
return 0; | |
} |
# CF914G Sum the Fibonacci
看到题目中有许多条件,但是不要慌,我们把这些条件一个一个拆开来看。
题目要求对满足 ,且 的五元组求 ( 为斐波那契数列)。
我们令 ,那么 要满足 ,我们求的就是 。
即:
第一个 的求和就是一个子集卷积,中间的 就是个桶,最后面的 的求和就是一个异或卷积。
所以直接把上面几道题的板子复制过来改改就过了。
那么这里还是贴一下代码。
#include <bits/stdc++.h> | |
#define ll long long | |
using namespace std; | |
namespace IO{ | |
inline int read(){ | |
int x = 0; | |
char ch = getchar(); | |
while(!isdigit(ch)) ch = getchar(); | |
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar(); | |
return x; | |
} | |
template <typename T> inline void write(T x){ | |
if(x > 9) write(x / 10); | |
putchar(x % 10 + '0'); | |
} | |
} | |
using namespace IO; | |
const int N = 1 << 17; | |
const int mod = 1e9 + 7; | |
const int inv2 = 500000004; | |
int n, ans; | |
int a[N], b[N], c[N]; | |
int f[20][N], h[N], g[N], fib[N], cnt[N]; | |
inline int add(int x) {return x >= mod ? x - mod : x;} | |
inline int sub(int x) {return x < 0 ? x + mod : x;} | |
inline int mul(int x, int y) {return 1ll * x * y % mod;} | |
inline int calc(int x) {return (x % mod + mod) % mod;} | |
inline void init(){ | |
fib[1] = cnt[1] = 1; | |
for(int i = 2; i < N; ++i) | |
fib[i] = add(fib[i - 1] + fib[i - 2]), cnt[i] = cnt[i >> 1] + (i & 1); | |
} | |
inline void fwt_or(int *f, int typ){ | |
for(int k = 1; k < N; k <<= 1) | |
for(int i = 0; i < N; i += (k << 1)) | |
for(int j = 0; j < k; ++j) | |
f[i + j + k] = calc(f[i + j + k] + 1ll * typ * f[i + j]); | |
} | |
inline void fwt_and(int *f, int typ){ | |
for(int k = 1; k < N; k <<= 1) | |
for(int i = 0; i < N; i += (k << 1)) | |
for(int j = 0; j < k; ++j) | |
f[i + j] = calc(f[i + j] + 1ll * typ * f[i + j + k]); | |
} | |
inline void fwt_xor(int *f, int typ){ | |
for(int k = 1; k < N; k <<= 1) | |
for(int i = 0; i < N; i += (k << 1)) | |
for(int j = 0; j < k; ++j){ | |
int x = f[i + j], y = f[i + j + k]; | |
f[i + j] = mul(typ, add(x + y)), f[i + j + k] = mul(typ, sub(x - y)); | |
} | |
} | |
signed main(){ | |
n = read(), init(); | |
for(int i = 1, x; i <= n; ++i) x = read(), a[x]++, b[x]++, c[x]++; | |
for(int i = 0; i < N; ++i) f[cnt[i]][i] = a[i], a[i] = 0; | |
for(int i = 0; i < 18; ++i) fwt_or(f[i], 1); | |
for(int i = 0; i < 18; ++i){ | |
memset(h, 0, sizeof(h)); | |
for(int j = 0; j <= i; ++j) | |
for(int k = 0; k < N; ++k) | |
h[k] = add(h[k] + 1ll * f[j][k] * f[i - j][k] % mod); | |
fwt_or(h, -1); | |
for(int j = 0; j < N; ++j) | |
if(i == cnt[j]) a[j] = add(a[j] + h[j]); | |
} | |
fwt_xor(c, 1); | |
for(int i = 0; i < N; ++i) c[i] = mul(c[i], c[i]); | |
fwt_xor(c, inv2); | |
for(int i = 0; i < N; ++i) a[i] = mul(a[i], fib[i]), b[i] = mul(b[i], fib[i]), c[i] = mul(c[i], fib[i]); | |
fwt_and(a, 1), fwt_and(b, 1), fwt_and(c, 1); | |
for(int i = 0; i < N; ++i) g[i] = mul(a[i], mul(b[i], c[i])); | |
fwt_and(g, -1); | |
for(int i = 1; i < N; i <<= 1) ans = add(ans + g[i]); | |
write(ans), puts(""); | |
return 0; | |
} |
# CF662C Binary Table
非常巧妙的题。
首先观察到 但是 是什么鬼?
先想一个比较显然的暴力,我们 枚举每一行是否翻转,然后每一列的状态就都是已知的了, 枚举所有的列,答案加上当前列 出现次数较小值即可,时间复杂度 。
那么显然这个 实在是太大了,考虑如何去掉 。
一共有 种列状态,每种状态都是 里的一种,我们记 表示状态 出现的次数, 表示状态 中 出现次数的较小值。
假设当前枚举的行状态为 ,那么答案就是:
状态为 的有 列,翻转之后每一列上 1 的个数都为 。
设 表示状态为 时的答案,那么有:
所以求一下 和 的异或卷积,然后统计答案即可。
#include <bits/stdc++.h> | |
#define int long long | |
using namespace std; | |
namespace IO{ | |
inline int read(){ | |
int x = 0; | |
char ch = getchar(); | |
while(!isdigit(ch)) ch = getchar(); | |
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar(); | |
return x; | |
} | |
template <typename T> inline void write(T x){ | |
if(x > 9) write(x / 10); | |
putchar(x % 10 + '0'); | |
} | |
} | |
using namespace IO; | |
const int N = (1 << 20) + 10; | |
const int M = 1e5 + 10; | |
const int inf = 1e18; | |
int n, m, lim, ans = inf; | |
char s[M]; | |
int f[N], g[M], a[N], b[N]; | |
inline int calc(int x) { | |
int cnt = __builtin_popcount(x); | |
return min(cnt, n - cnt); | |
} | |
inline void fwt_xor(int f[], int typ){ | |
for(int k = 1; k < lim; k <<= 1) | |
for(int i = 0; i < lim; i += (k << 1)) | |
for(int j = 0; j < k; ++j){ | |
int x = f[i + j], y = f[i + j + k]; | |
if(typ == 1) f[i + j] = x + y, f[i + j + k] = x - y; | |
else f[i + j] = (x + y) >> 1, f[i + j + k] = (x - y) >> 1; | |
} | |
} | |
signed main(){ | |
n = read(), m = read(), lim = 1 << n; | |
for(int i = 0; i < n; ++i){ | |
scanf("%s", s); | |
for(int j = 0; j < m; ++j) | |
if(s[j] == '1') g[j] |= (1 << i); | |
} | |
for(int i = 0; i < m; ++i) a[g[i]]++; | |
for(int i = 0; i < lim; ++i) b[i] = calc(i); | |
fwt_xor(a, 1), fwt_xor(b, 1); | |
for(int i = 0; i < lim; ++i) f[i] = a[i] * b[i]; | |
fwt_xor(f, -1); | |
for(int i = 0; i < lim; ++i) ans = min(ans, f[i]); | |
write(ans), puts(""); | |
return 0; | |
} |
# CF850E Random Elections
这是我见过的最巧妙的 题目。
题意就不解释了。
显然一轮中赢两次的玩家之后一个,所以计算一个人获胜的次数乘 3 即可。
考虑一个人如何才能获胜。
对于两场比赛 (题目要求), 的的票状态分别为 ,假设它们第 位的情况为 ( ),那么有 4 中情况:
后面的字母串表示这个人可能的投票顺序。
根据乘法原理, 和 都是没有贡献的(均为乘 1), 和 均有 2 的贡献。
所以设 , 为 中 0 的个数,那么方案数就是 。
给 赋好初值以后,不难发现这东西就是一个 自己和自己的异或卷积!
于是这题就愉快的解决了。
#include <bits/stdc++.h> | |
using namespace std; | |
namespace IO{ | |
inline int read(){ | |
int x = 0; | |
char ch = getchar(); | |
while(!isdigit(ch)) ch = getchar(); | |
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar(); | |
return x; | |
} | |
template <typename T> inline void write(T x){ | |
if(x > 9) write(x / 10); | |
putchar(x % 10 + '0'); | |
} | |
} | |
using namespace IO; | |
const int N = (1 << 20) + 10; | |
const int mod = 1e9 + 7; | |
const int inv2 = 500000004; | |
int n, lim, ans; | |
char s[N]; | |
int f[N], g[N]; | |
inline int add(int x) {return x >= mod ? x - mod : x;} | |
inline int mul(int x, int y) {return 1ll * x * y % mod;} | |
inline int qpow(int a, int b){ | |
int res = 1; | |
while(b){ | |
if(b & 1) res = mul(res, a); | |
a = mul(a, a), b >>= 1; | |
} | |
return res; | |
} | |
inline void fwt_xor(int f[], int typ){ | |
for(int k = 1; k < lim; k <<= 1) | |
for(int i = 0; i < lim; i += (k << 1)) | |
for(int j = 0; j < k; ++j){ | |
int x = f[i + j], y = f[i + j + k]; | |
f[i + j] = mul(x + y, typ), f[i + j + k] = mul(x - y, typ); | |
} | |
} | |
signed main(){ | |
n = read(), lim = 1 << n; | |
scanf("%s", s); | |
for(int i = 0; i < lim; ++i) f[i] = s[i] - '0'; | |
fwt_xor(f, 1); | |
for(int i = 0; i < lim; ++i) f[i] = mul(f[i], f[i]); | |
fwt_xor(f, inv2); | |
for(int i = 0; i < lim; ++i) | |
ans = add(ans + mul(f[i], qpow(2, n - __builtin_popcount(i)))); | |
write(mul(3, ans)), puts(""); | |
return 0; | |
} |