集合/二进制运算合集
RT,主要内容涉及有 高维前缀和(子集DP),高维后缀和,高维差分,快速沃尔什变换,子集卷积。
参考资料:
知识点合集
高维前缀和
用于求解
for(int i=0;i<(1<<n);i++)f[i]=g[i];
for(int j=0;j<n;j++){
for(int i=0;i<(1<<n);i++)if((i>>j)&1)f[i]+=f[i^(1<<j)];
}
本质原理:DP。统计依次前
高维差分
考虑子集反演
根据这个,其实可以考虑 FWT 进行计算,不过过于累赘。
我们发现这玩意在高维前缀和把加号换成减号容斥系数正好正确。
for(int i=0;i<(1<<n);i++)g[i]=f[i];
for(int j=0;j<n;j++){
for(int i=0;i<(1<<n);i++)if((i>>j)&1)g[i]-=g[i^(1<<j)];
}
快速沃尔什变换
应用于计算二进制卷积。
卷积
计算
我们定义
则有:
所以我们只需要考虑
考虑钦定长度为
定义将
不难发现
而
(事实上,从FMT的视角来看,这玩意是高维前缀和)
卷积
类比 or,设计
而根据分治法的结果,同样有:
(高维后缀和)
卷积
这个比较抽象,设
设
根据
同样有:
考虑计算
容易发现
所以有
因此有
写法:考虑类比FFT,迭代换递归,但由于位置始终不变,所以不需要蝴蝶变换。
#include<bits/stdc++.h>
using namespace std;
#define N (1<<17)+5
const int p=998244353;
int n;
void ad(int &x,int y){
x+=y;if(x>p)x-=p;if(x<0)x+=p;
}
void fwtor(int a[],int d,int tag){
for(int len=1;len<(1<<d);len<<=1){
for(int j=0;j<(1<<d);j+=(len<<1)){
for(int k=0;k<len;++k)ad(a[j+k+len],a[j+k]*tag);
}
}
}
void fwtand(int a[],int d,int tag){
for(int len=1;len<(1<<d);len<<=1){
for(int j=0;j<(1<<d);j+=(len<<1)){
for(int k=0;k<len;++k)ad(a[j+k],a[j+k+len]*tag);
}
}
}
void fwtxor(int a[],int d,int tag){
for(int len=1;len<(1<<d);len<<=1){
for(int j=0;j<(1<<d);j+=(len<<1)){
for(int k=0;k<len;++k){
int x=a[j+k],y=a[j+k+len];
a[j+k]=1ll*(x+y)%p*tag%p;
a[j+k+len]=1ll*(p+x-y)%p*tag%p;
}
}
}
}
int a[N],b[N],c[N],tmpa[N],tmpb[N];
int main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin>>n;for(int i=0;i<(1<<n);++i)cin>>a[i];
for(int i=0;i<(1<<n);i++)cin>>b[i];
memcpy(tmpa,a,sizeof a);memcpy(tmpb,b,sizeof b);
fwtor(a,n,1);fwtor(b,n,1);
for(int i=0;i<(1<<n);i++)c[i]=1ll*a[i]*b[i]%p;
fwtor(c,n,-1);
for(int i=0;i<(1<<n);i++)cout<<c[i]<<" ";cout<<"\n";
memcpy(a,tmpa,sizeof a);memcpy(b,tmpb,sizeof b);
fwtand(a,n,1);fwtand(b,n,1);
for(int i=0;i<(1<<n);i++)c[i]=1ll*a[i]*b[i]%p;
fwtand(c,n,-1);
for(int i=0;i<(1<<n);i++)cout<<c[i]<<" ";cout<<"\n";
memcpy(a,tmpa,sizeof a);memcpy(b,tmpb,sizeof b);
fwtxor(a,n,1);fwtxor(b,n,1);
for(int i=0;i<(1<<n);i++)c[i]=1ll*a[i]*b[i]%p;
fwtxor(c,n,(p+1)>>1);
for(int i=0;i<(1<<n);i++)cout<<c[i]<<" ";cout<<"\n";
}
注意到 IFWT Xor 我们可以最后再除掉
注意到三个卷积的形式其实是一样的,也就是说我们会有:
-
-
前面的乘法是做异或卷积,后面是对位相乘
子集卷积
求
考虑到这等价于:
而
这里考虑
所以设
则有
然后
#include<bits/stdc++.h>
using namespace std;
int a[20][1<<20],b[20][1<<20],c[20][1<<20];
#define bit __builtin_popcount
const int p=1e9+9;
void ad(int &x,int y){
x+=y;if(x>p)x-=p;
if(x<0)x+=p;
}
void fwtor(int a[],int n,int tag){
for(int len=1;len<(1<<n);len<<=1)for(int j=0;j<(1<<n);j+=len<<1)for(int k=0;k<len;k++)ad(a[j+k+len],a[j+k]*tag);
}
int read(){
int x=0;char ch=getchar();
while(ch>'9'||ch<'0')ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x;
}
void print(int x){
if(x<10)putchar(x+'0');
else print(x/10),putchar(x%10+'0');
}
int main(){
int n=read();
for(int i=0;i<(1<<n);i++)a[bit(i)][i]=read();
for(int i=0;i<(1<<n);i++)b[bit(i)][i]=read();
for(int i=0;i<=n;i++)fwtor(a[i],n,1),fwtor(b[i],n,1);
for(int k=0;k<=n;k++)for(int i=0;i<=k;i++)for(int x=0;x<(1<<n);x++)ad(c[k][x],1ll*a[i][x]*b[k-i][x]%p);
for(int k=0;k<=n;k++)fwtor(c[k],n,-1);
for(int i=0;i<(1<<n);i++)print((c[bit(i)][i]%p+p)%p),putchar(' ');
}
例题
ABC212H
模版题,可以容斥计算。
令
等比数列计算最后 IFWT 回去即可。
注意特判
P5387 人形演舞
博弈论结合FWT,一般用于求方案数,可以先求出每个子游戏的 sg 函数。
打表看 sg 函数,发现
至于它要选出
设
求出
州区划分
注意不连通是合法的
首先求出
考虑dp,设
考虑如何使用
注意到子集卷积的每一步有一个
And-Or Game
题意:给定值域为正整数的集合
- 选择
, - 选择
你可以进行任意次操作,求可能达到的
首先转化为表达方案数,我们合并连续的使用
然后我们现在就是两个操作交替进行了,那么考虑一个答案是如何生成的,显然是在 or
时至少让某位的 0->1
,在 and
时至少让某位 1->0
,并且不会被抵消。
这就告诉我们最多不超过
CF582E
题面较长自行阅读。
根据输入显然知道要建立表达式树。直接用栈建就行了。
然后考虑我们设
则可以预处理出单独每个字符的取值,在叶子节点的时候赋值。当如果是 ?
那就直接加上所有。
然后合并子树根据运算符类型用 fwtor fwtand 优化合并即可。问号就齐上。
二进制卷积优化DP转移
CF1326F2
很好的数学,使我的大脑旋转。
,给定 矩阵 ,保证 ,考虑每个 的排列 ,定义 串 , 。问对于 种 串,生成其的排列个数各有多少。
注意到关键限制是
考虑子集反演,本质上只考虑钦定某些位置为
则
考虑计算
这告诉我们,所有
也就是例如
由此,我们发现答案至多有
所以呢?这就有点子集卷积的味道了。
考虑对于每个
设
显然
然后对
其实这个整数划分对应的是子集卷积,只不过钦定加入大小为多少的子集而已。
根据
然后在整数划分的过程中进行子集卷积,最后我们只关心全集的值,可以直接子集反演掉(FWTOR 本质是子集求和,这里逆差分就行)。
在求出每种情况的值之后就求得了
高维前缀和,高维差分,高维后缀和,FWTOR,FWTAND,FMTOR,FMTAND 本质与子集反演都是相通的。
code
#include<bits/stdc++.h>
using namespace std;
#define N 18
#define int long long
// const int p=998244353;
#define bit __builtin_popcount
int f[(1<<18)+5],n,m,dp[(1<<18)+5][18],c[19][(1<<18)+5],dep,g[19][(1<<18)+5];
int rch[N];
void fwtor(int a[],int n,int tag){
for(int len=1;len<(1<<n);len<<=1){
for(int j=0;j<(1<<n);j+=len<<1){
for(int k=0;k<len;k++){
a[j+k+len]+=a[j+k]*tag;
// a[j+k+len]%=p;
}
}
}
}
map<vector<int> ,vector<int>>h;
vector<int>now;
void dfs(int sur,int lst){
if(!sur){
/*考虑到FWT or 的本质是高维前缀和,我们只要h[(1<<n)-1]那么这里做子集反演*/
int res=0;
for(int i=0;i<(1<<n);++i){
if(bit(((1<<n)-1)^i)&1)res-=g[dep][i];
else res+=g[dep][i];
}
// cout<<res<<"\n";
for(auto i:h[now]){
f[i]+=res;
// cout<<" "<<i;
}
// cout<<"\n";
return ;
}
for(int i=1;i<=min(lst,sur);i++){
for(int j=0;j<(1<<n);j++)g[dep+1][j]=g[dep][j]*c[i][j];
++dep;now.push_back(i);
dfs(sur-i,i);
--dep;now.pop_back();
}
}
signed main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin>>n;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
char x;cin>>x;
if(x=='1')rch[i]|=(1<<j);
}
}
for(int i=0;i<n;i++)dp[1<<i][i]=1;
for(int i=0;i<(1<<n);i++){
for(int j=0;j<n;j++)if((i>>j)&1)for(int k=0;k<n;k++)if(((i>>k)&1)^1){
if((rch[j]>>k)&1)dp[i|(1<<k)][k]+=dp[i][j];
}
}
for(int i=0;i<(1<<n);i++)for(int j=0;j<n;j++)c[bit(i)][i]+=dp[i][j];
for(int i=0;i<=n;i++)fwtor(c[i],n,1);
for(int i=0;i<(1<<n-1);i++){//2^{n-1}种01串
vector<int>now;
int c=1;
for(int j=0;j<n-1;j++){
if((i>>j)&1)++c;
else now.push_back(c),c=1;
}
now.push_back(c);sort(now.begin(),now.end());reverse(now.begin(),now.end());h[now].push_back(i);
}
for(int i=0;i<(1<<n);i++)g[0][i]=1;
dfs(n,n);
for(int i=0;i<n-1;i++)for(int j=0;j<(1<<n-1);++j)if((j>>i)&1)f[j^(1<<i)]-=f[j];
for(int i=0;i<(1<<n-1);i++)cout<<f[i]<<" ";
}
ABC367 G
神奇题目
场上想到了引入多元生成函数之后就嗝屁了。
定义两个多项式的运算
定义两个二元生成函数
我们仍然选用
显然
而
现在问题在于计算
这其实是
如果我们先不考虑
根据这个我们可以计算
但是带上了
而
所以实际上拿出
显然我们还是得到了一个二维多项式,且可以有
也可以佐证上述结论。
所以我们对于每个
怎么拿?不妨设
那么
ZJOI2019 开关
考场压根没有想 FWT
首先还是把
设
考虑 FWT。
设
有:
注意这里的
既然有异或卷积定然考虑
根据
定义,有
所以有
由于
考虑
又,我们需要知道
回带有:
后面容易背包
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!