Set algorithm
Set algorithm
Sometimes we may meet with some functions \(S\rightarrow V\)(S is a set,V is a value),and we need to handle some transformations on it.
The article is for beginners whick is much easy, and I may continously update the article.
There are some features.
First the problem always give small \(n\),such as \(n\leq 15,n\leq20\).
Second,the time complexity is always \(O(n2^n),O(3^n),O(n^22^n)\).
Let's do some assumption:
The values form a ring and they contain ring operations like addition,substraction,multification or division in constant time.
FWT/FMT
The most classical one is FWT/FMT.
Problem
Define \(a(S),b(S)(S\subseteq U)\) on a set.
Get all \(c(S)=\sum\limits_{i \oplus j=S}a_ib_j\qquad \oplus \text{is} \ \textrm{and,or,xor}\)
The general idea:
Let \(a, b, c\) be maps (functions) from a set \(S\) to a value domain \(V=(N/Q/C\cdots)\). We define a transformation \(\text{FWT}\) (Fast Walsh Transform) as a map from the space of such functions to itself, i.e.,
with the following properties:
- Invertibility: \(\text{FWT}\) has an inverse map \(\text{FWT}^{-1}\).
- Efficiency: \(\text{FWT}\) can be computed in \(O(n \log n)\) time.
- Pointwise product property: For all \(i \in S\),\[\text{FWT}(a)_i \cdot \text{FWT}(b)_i = \text{FWT}(c)_i, \]
Calculating \(\mathrm{FWT(a),FWT(b)}\),then pointwise product between them and do the \(\mathrm{FWT}^{-1}\),it's \(c\).
I like cpp,so I use | for \(\textrm{or}\),& for \(\textrm{and}\),^ for \(\textrm{xor}\).
OR FWT/FMT
a.k.a Zeta Transform,SOS DP,Yate's DP,Mobius Transform
The calculation is like this.
for(int o=1;o*2<=n;o<<=1)
for(int i=0;i<n;i+=o<<1)
for(int j=0;j<o;++j)
a[i+j+o]=a[i+j+o]+a[i+j];
We first split the \(a\) correspond to the current bit to \(a_0,a_1\).Assume we are process the x-th bit,in the \(a_k\) the x-th bit is \(k\).We do a divide and conquer.Then
We difine the addition on \((S\rightarrow V)\) is addition on each on single element.
And its invertion transform is easy to find.
\(\textrm{FWT}^{-1}\) a.k.a inverse Mobius Transform.
And verify the Pointwise product property.
Afterwards,I will omit some details.
AND FWT/FMT
for(int o=1;o*2<=n;o<<=1)
for(int i=0;i<n;i+=o<<1)
for(int j=0;j<o;++j)
a[i+j]=a[i+j]+a[i+j+o];
It's the same as OR,because we can flip all the bits at the beginning and flip them back at the end.
XOR FWT/FMT
define \(W(x,i)=popcount(x\wedge i)\bmod 2\)
which satisfy \(W(x,i\wedge j)=W(x,i)\wedge W(x,j)\)
for(int o=1;o*2<=n;o<<=1)
for(int i=0;i<n;i+=o<<1)
for(int j=0;j<o;++j){
ll x=f[i+j],y=f[i+j+o];
f[i+j]=(x+y);
f[i+j+o]=(x-y);
}
Subset Sum Convolution
Problem
Define \(a(S),b(S)(S\subseteq U)\) on a set.
Get all \(c(S)=\sum\limits_{i\subseteq S}a(i)b(S\setminus i)\)
The general idea by Intuition:
for \(S\),calculate sum \(a_ib_j\) of for all \(i,j(|i|+|j|=S)\).
and only if \(|i\cap j|=|S|\), it's valid. So do apply inclusion-exclusion principle on it,we can get the result.
Ranked Mobius Transform and inversion
define
the inverse
Fast Subset Convolution
define
one can show that
for(int mask = 0; mask < (1 << n); mask++) {
fa[__builtin_popcount(mask)][mask] = a[mask];
fb[__builtin_popcount(mask)][mask] = b[mask];
}
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
for(int mask = 0; mask < (1 << n); mask++) {
if((mask & (1 << j)) != 0) {
fa[i][mask] += fa[i][mask ^ (1 << j)];
fb[i][mask] += fb[i][mask ^ (1 << j)];
}
}
}
}
for(int mask = 0; mask < (1 << n); mask++) {
for(int i = 0; i < n; i++) {
for(int j = 0; j <= i; j++) {
d[i][mask] += fa[j][mask] * fb[i - j][mask];
}
}
}
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
for(int mask = 0; mask < (1 << n); mask++) {
if((mask & (1 << j)) != 0) {
h[i][mask] -= h[i][mask ^ (1 << j)];
}
}
}
}
for(int mask = 0; mask < (1 << n); mask++) c[mask] = h[__builtin_popcount(mask)][mask];
In the \(\sum_{i\subseteq S}(-1)^{|S|-|i|}\sum_j \hat{a}(x,i)\hat{b}(|i|-x,i)\).
for exact \(S\), a pair of \(U,V\) in \(\hat{a},\hat{b}\) is calculated for
times.
So it's right.
Exercise
CF1713F
reorder \(a_i=a_{n-i}\)
get the diagnal c_i.
\(a\rightarrow c \rightarrow b\) is a series of transforms ,so just do the inverse.
and calculate the contribution
QOJ13083
We see if \(f(P)\) is certain,the mask of \(P\) is easier to describe.
now the \(f(P),f(Q)\) is limited by \(S,T\),so enumerate \(S,T\).
Do the FWT convolution (OR) \(\hat{f}\times \hat{f}\)
and handle some details.
Counting on the graph
Counting Tree Subgraph (No problem,compared with Matrix-Tree)
Given a simple undirected graph, count how many subgraph of it is a tree.
Obviously, we can use Matrix-Tree Theorem to solve it.
Input:
\(n\) is number of vertixs, \(m\) is the number of edges,and note the points are 1-indexed,output the \(ans \bmod 992844353\).
The Matrix-Tree One
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll MOD = 998244353;
ll mod_pow(ll a, ll b) {
ll res = 1;
while (b) {
if (b & 1) res = res * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return res;
}
ll mod_inv(ll a) {
return mod_pow(a, MOD - 2);
}
ll det_mod(vector<vector<ll>> &mat, int n) {
ll res = 1;
for (int i = 0; i < n; i++) {
int p = -1;
for (int j = i; j < n; j++) {
if (mat[j][i]) {
p = j;
break;
}
}
if (p == -1) return 0;
if (p != i) {
swap(mat[i], mat[p]);
res = (MOD - res) % MOD;
}
res = res * mat[i][i] % MOD;
ll inv = mod_inv(mat[i][i]);
for (int j = i; j < n; j++)
mat[i][j] = mat[i][j] * inv % MOD;
for (int j = i + 1; j < n; j++) {
if (!mat[j][i]) continue;
ll f = mat[j][i];
for (int k = i; k < n; k++)
mat[j][k] = (mat[j][k] - f * mat[i][k] % MOD + MOD) % MOD;
}
}
return res;
}
ll countST(int n, const vector<pair<int, int>> &e) {
if (n <= 1) return 1;
vector<vector<ll>> L(n, vector<ll>(n, 0));
vector<int> deg(n, 0);
for (auto [u, v] : e) {
u--, v--;
deg[u]++, deg[v]++;
}
for (int i = 0; i < n; i++)
L[i][i] = deg[i] % MOD;
for (auto [u, v] : e) {
u--, v--;
L[u][v] = (L[u][v] - 1 + MOD) % MOD;
L[v][u] = (L[v][u] - 1 + MOD) % MOD;
}
vector<vector<ll>> sub(n - 1, vector<ll>(n - 1, 0));
for (int i = 1; i < n; i++)
for (int j = 1; j < n; j++)
sub[i - 1][j - 1] = L[i][j];
return det_mod(sub, n - 1);
}
int main() {
int n, m;
cin >> n >> m;
vector<pair<int, int>> e(m);
for (int i = 0; i < m; i++)
cin >> e[i].first >> e[i].second;
cout << countST(n, e) << endl;
return 0;
}
//Generated by Deepseek
How can we do it manually?
It makes sense to set a root to better transition.
\(dp[i][S]\) means the number of trees rooted at \(i\) in the subgraph \(S\) .
Enumerate \(T\sub S\) with the lowest bit in \(S\oplus 2^{i}\)as a subtree from \(i\) and make transition.
#include<bits/stdc++.h>
using namespace std;
int n,m;
int e[20][20];
typedef long long ll;
ll f[20][(1<<20)+123];
const int P=998244353;
void madd(ll &x,ll y){x=(x+y)%P;}
int main(){
cin>>n>>m;
for(int i=1,u,v;i<=m;++i){
cin>>u>>v;
e[u-1][v-1]=e[v-1][u-1]=1;
}
for(int i=0;i<n;++i)f[i][1<<i]=1;
for(int mask=0;mask<1<<n;++mask)
for(int i=0;i<n;++i)if(mask>>i&1){
int smask=mask^(1<<i);
int lb=smask&(-smask);
for(int j=smask;j;j=(j-1)&smask){
if(!(j&lb))continue;
for(int k=0;k<n;++k)if((j>>k&1)&&e[i][k]){
madd(f[i][mask],f[k][j]*f[i][mask^j]);
}
}
}
printf("%lld\n",f[0][(1<<n)-1]);
return 0;
}
Observing that a tree can be rooted anywhere , we can only use \(dp[S]\).
#include<bits/stdc++.h>
using namespace std;
int n,m;
int e[20][20];
typedef long long ll;
ll f[(1<<20)+123];
const int P=998244353;
void madd(ll &x,ll y){x=(x+y)%P;}
ll inv[21];
int main(){
inv[1]=1;for(int i=2;i<=20;++i)inv[i]=P-inv[P%i]*(P/i)%P;
cin>>n>>m;
for(int i=1,u,v;i<=m;++i){
cin>>u>>v;
e[u-1][v-1]=e[v-1][u-1]=1;
}
for(int i=0;i<n;++i)f[1<<i]=1;
for(int mask=0;mask<1<<n;++mask)
for(int i=0;i<n;++i)if(mask>>i&1){
int smask=mask^(1<<i);
int lb=smask&(-smask);
for(int j=smask;j;j=(j-1)&smask){
if(!(j&lb))continue;
for(int k=0;k<n;++k)if((j>>k&1)&&e[i][k]){
madd(f[mask],f[j]*f[mask^j]%P*inv[__builtin_popcount(j)]%P*inv[__builtin_popcount(mask^j)]);
}
}
}
printf("%lld\n",f[(1<<n)-1]*inv[n]%P);
return 0;
}
Time complexity: \(O(n^23^n)\).
Space complexity: \(O(n2^n)\) or \(O(2^n)\).
Counting Cactus (QOJ7412)
It becomes more tricky.
\(f[i][S]\) means \(i\) is the root, \(i\) is connect to only one vertex or only in one cycle, the subtree is state \(S\).
\(g[i][j]\) means there is a path from \(i\) to \(j\) ,the subtree is state \(S\).
\(h[i][j]\) means \(i\) is the root, the subtree is state \(S\).
Transition:
- \(f\leftarrow h\) , a root connected to a tree
- \(f\leftarrow g\) a cycle is closed.
- \(g\leftarrow h\) extend the chain
- \(h\leftarrow f\) add cycles or subtrees to the root.
The final ans is \(h[x][S]\).
Overall time complexity \(O(n^23^n)\).
It's not good to delete the root dimension.
Counting SCC (Luogu P11714)
Consider PIE, calculate the number of DAG of scc which size is larger than \(2\).
If there is \(cnt\) scc which has \(0\) in-degree, give it \((-1)^{cnt}\) coef.
\(dp[S]\) means the subproblem for subgraph \(S\).
Handle some transition and it's done.
References
https://codeforces.com/blog/entry/45223
https://codeforces.com/blog/entry/72488
https://www.luogu.com.cn/problem/P4717
https://people.csail.mit.edu/rrw/presentations/subset-conv.pdf
https://www.luogu.com.cn/article/gs78uwd7
https://www.cnblogs.com/Mackerel-Pike/p/16395436.html
https://yhx-12243.github.io/OI-transit/records/loj6719%3Bgym102331C.html
Others
Looking forward to your upvote!

浙公网安备 33010602011771号