位运算卷积
有点错,原来 or 和 and 的卷积叫 FMT,xor 的卷积叫 FWT。我之前学的是什么东西。
or 卷积
集合并乘法:。
设 ,也就是高维前缀和。 就是高维差分。
有 和 。
and 卷积 则是向低维去前缀和。
UPD:但是,事实上,FWT 版本的写法常数小得多。
xor 卷积
设 。然后可以递归:。
void fmtor(int *a,int n,int w=1){
for(int i=0;i<n;i++){
for(int s=0;s<(1<<n);s++)if(s&(1<<i))(a[s]+=a[s^(1<<i)]*w)%=mod;
}
}
void fmtand(int *a,int n,int w=1){
for(int i=0;i<n;i++){
for(int s=0;s<(1<<n);s++)if(s&(1<<i))(a[s^(1<<i)]+=a[s]*w)%=mod;
}
}
void fwtxor(int *a,int n,int fl=1){
for(int l=2;l<=n;l<<=1){
int k=l>>1;
for(int i=0;i<n;i+=l){
for(int j=i;j<i+k;j++){
int u=a[j],v=a[j+k];
a[j]=(u+v)*fl%mod,a[j+k]=(u+mod-v)*fl%mod;
}
}
}
}
int f[1<<17],g[1<<17];
void mulor(int *a,int *b,int *ans,int n){
for(int i=0;i<(1<<n);i++)f[i]=a[i],g[i]=b[i];
fmtor(f,n),fmtor(g,n);
for(int i=0;i<(1<<n);i++)(f[i]*=g[i])%=mod;
fmtor(f,n,mod-1);
for(int i=0;i<(1<<n);i++)ans[i]=f[i],f[i]=g[i]=0;
}
void muland(int *a,int *b,int *ans,int n){
for(int i=0;i<(1<<n);i++)f[i]=a[i],g[i]=b[i];
fmtand(f,n),fmtand(g,n);
for(int i=0;i<(1<<n);i++)(f[i]*=g[i])%=mod;
fmtand(f,n,mod-1);
for(int i=0;i<(1<<n);i++)ans[i]=f[i],f[i]=g[i]=0;
}
void mulxor(int *a,int *b,int *ans,int n){
for(int i=0;i<(1<<n);i++)f[i]=a[i],g[i]=b[i];
fwtxor(f,1<<n),fwtxor(g,1<<n);
for(int i=0;i<(1<<n);i++)(f[i]*=g[i])%=mod;
fwtxor(f,1<<n,(mod+1)/2);
for(int i=0;i<(1<<n);i++)ans[i]=f[i],f[i]=g[i]=0;
}子集卷积
集合无交并乘法:。
等价于 ,所以取 ,则 。也称为占位多项式。
也即拆为 个集合幂级数,集合幂级数做集合并乘法,集合幂级数间看做形式幂级数做加乘卷积。
逆着做形式幂级数部分就是 求逆。
还可以拓展。对于只要求一部分无交,可以对 做占位多项式。见 CF2070F。
exp & ln
弄出占位幂级数,对集合幂级数一维 fmt,对形式幂级数一维做 的 ln 和 exp。
把 当成集合幂级数,设 ,则 ,其中乘法为无交并。exp 的组合意义即有序取 个子集再消除顺序,可以对应到 枚举子集的卷积 。ln 则为其逆运算。
注意到在保证 的时候,exp 只用保留前 项。
Code
注意访问连续性。
inline void inc(int &u,int v){((u+=v)>=mod)&&(u-=mod);}
int ff[maxn+1][1<<maxn],gg[maxn+1][1<<maxn];
void fmt1(int *a,int n){
for(int l=2;l<=n;l<<=1){
int k=l>>1;
for(int i=0;i<n;i+=l){
for(int j=i;j<i+k;j++)inc(a[j+k],a[j]);
}
}
}
void fmt2(int *a,int n){
for(int l=2;l<=n;l<<=1){
int k=l>>1;
for(int i=0;i<n;i+=l){
for(int j=i;j<i+k;j++)inc(a[j+k],mod-a[j]);
}
}
}
int tf[maxn+1],tg[maxn+1],hh[maxn+1],ni[maxn+1];
void xormul(int *a,int *b,int *c,int n){
for(int i=0;i<=n;i++){
for(int s=0;s<(1<<n);s++)ff[i][s]=gg[i][s]=0;
}
for(int s=0;s<(1<<n);s++)ff[__builtin_popcount(s)][s]=a[s];
for(int s=0;s<(1<<n);s++)gg[__builtin_popcount(s)][s]=b[s];
for(int i=0;i<=n;i++)fmt1(ff[i],1<<n);
for(int i=0;i<=n;i++)fmt1(gg[i],1<<n);
for(int s=0;s<(1<<n);s++){
for(int i=0;i<=n;i++)tf[i]=ff[i][s];
for(int i=0;i<=n;i++)tg[i]=gg[i][s];
for(int i=0;i<=n;i++){
hh[i]=0;
for(int j=0;j<=i;j++)inc(hh[i],1ll*tf[j]*tg[i-j]%mod);
}
for(int i=0;i<=n;i++)ff[i][s]=hh[i];
}
for(int i=0;i<=n;i++)fmt2(ff[i],1<<n);
for(int s=0;s<(1<<n);s++)inc(c[s],ff[__builtin_popcount(s)][s]);
}
void xorni(int *a,int *b,int n){//b=1/a
for(int i=0;i<=n;i++){
for(int s=0;s<(1<<n);s++)ff[i][s]=0;
}
for(int s=0;s<(1<<n);s++)ff[__builtin_popcount(s)][s]=a[s];
for(int i=0;i<=n;i++)fmt1(ff[i],1<<n);
for(int s=0;s<(1<<n);s++){
for(int i=0;i<=n;i++)tf[i]=ff[i][s];
int nif=ksm(tf[0]);
for(int i=0;i<=n;i++){
hh[i]=1;
for(int j=1;j<=i;j++)inc(hh[i],mod-1ll*tf[j]*hh[i-j]%mod);
hh[i]=1ll*hh[i]*nif%mod;
}
for(int i=0;i<=n;i++)ff[i][s]=hh[i];
}
for(int i=0;i<=n;i++)fmt2(ff[i],1<<n);
for(int s=0;s<(1<<n);s++)b[s]=ff[__builtin_popcount(s)][s];
}
void xorexp(int *a,int *b,int n){//exp(a)=b
ni[0]=ni[1]=1;for(int i=2;i<=n;i++)ni[i]=1ll*(mod-mod/i)*ni[mod%i]%mod;
for(int i=0;i<=n;i++){
for(int s=0;s<(1<<n);s++)ff[i][s]=0;
}
for(int s=0;s<(1<<n);s++)ff[__builtin_popcount(s)][s]=a[s];
for(int i=0;i<=n;i++)fmt1(ff[i],1<<n);
for(int s=0;s<(1<<n);s++){
for(int i=0;i<=n;i++)hh[i]=0;
for(int i=0;i<=n;i++)tf[i]=ff[i][s];
for(int i=0;i<=n;i++){
if(i<n)hh[i]=1ll*tf[i+1]*(i+1)%mod;
for(int j=1;j<=i;j++)inc(hh[i],1ll*tf[j]*j%mod*hh[i-j]%mod*ni[i-j+1]%mod);
}
for(int i=1;i<=n;i++)ff[i][s]=1ll*hh[i-1]*ni[i]%mod;
}
for(int i=0;i<=n;i++)fmt2(ff[i],1<<n);
b[0]=1;for(int s=1;s<(1<<n);s++)b[s]=ff[__builtin_popcount(s)][s];
}
void xorln(int *a,int *b,int n){//ln(a)=b
ni[0]=ni[1]=1;for(int i=2;i<=n;i++)ni[i]=1ll*(mod-mod/i)*ni[mod%i]%mod;
for(int i=0;i<=n;i++){
for(int s=0;s<(1<<n);s++)ff[i][s]=0;
}
for(int s=0;s<(1<<n);s++)ff[__builtin_popcount(s)][s]=a[s];
for(int i=0;i<=n;i++)fmt1(ff[i],1<<n);
for(int s=0;s<(1<<n);s++){
for(int i=0;i<=n;i++)hh[i]=0;
for(int i=0;i<=n;i++)tf[i]=ff[i][s];
for(int i=0;i<n;i++){
hh[i]=1ll*tf[i+1]*(i+1)%mod;
for(int j=1;j<=i;j++)inc(hh[i],mod-1ll*tf[j]*hh[i-j]%mod);
}
for(int i=1;i<=n;i++)ff[i][s]=1ll*hh[i-1]*ni[i]%mod;
}
for(int i=0;i<=n;i++)fmt2(ff[i],1<<n);
b[0]=0;for(int s=1;s<(1<<n);s++)b[s]=ff[__builtin_popcount(s)][s];
}半在线子集卷积
按 从小到大分批转移。复杂度 。
非质数模数 exp & ln
直接 fmt 然后 做形式幂级数运算需要求逆元,不一定有。
从组合意义出发。
exp:。那就挖掉 high bit 然后对剩下的子集卷积即可。
void exp(int *a,int *b,int n){
b[0]=1;
for(int i=0;i<n;i++)xormul(a+(1<<i),b,b+(1<<i),i);
}ln:。那就挖掉 high bit 然后对剩下的半在线子集卷积。
void mulself(int *a,int *b,int n){
for(int i=0;i<=n;i++){
for(int s=0;s<(1<<n);s++)ff[i][s]=gg[i][s]=0;
}
for(int s=0;s<(1<<n);s++)ff[__builtin_popcount(s)][s]=a[s];
for(int s=0;s<(1<<n);s++)gg[__builtin_popcount(s)][s]=b[s];
for(int i=0;i<=n;i++)fmt1(ff[i],1<<n);
for(int i=0;i<=n;i++)fmt1(gg[i],1<<n);
for(int i=0;i<=n;i++){
fmt2(gg[i],1<<n);
for(int s=0;s<(1<<n);s++)if(__builtin_popcount(s)==i)gg[i][s]=mod-gg[i][s],inc(gg[i][s],a[s|(1<<n)]);
fmt1(gg[i],1<<n);
for(int j=i+1;j<=n;j++){
for(int s=0;s<(1<<n);s++)inc(gg[j][s],1ll*gg[i][s]*ff[j-i][s]%mod);
}
}
for(int i=0;i<=n;i++)fmt2(gg[i],1<<n);
for(int s=0;s<(1<<n);s++)b[s]=gg[__builtin_popcount(s)][s];
}
void ln(int *a,int *b,int n){
// for(int s=1;s<(1<<n);s++){
// int k=__lg(s);b[s]=a[s];
// for(int t=(s-1)&s;t;t=(t-1)&s)if(t&(1<<k))b[s]-=b[t]*a[s^t];
// }
for(int i=0;i<n;i++)mulself(a,b+(1<<i),i);
}exp 会比普通版本快一点?
更进一步?
如果不仅没有逆元,还不可减,如何呢?
可能必须要题目有特殊的性质。
划分为若干个集合,每个集合重量 ,集合权值为元素最大值,最小化所有集合权值之和,求方案数。
。运算为 。形式为 exp。
按 排序,权值只与 有关。
折半。有 ,枚举 ,同时考虑所有 向 的转移。提前预处理 的超集和 的子集,按 排序,双指针,复杂度 。
或许可以再研究一下。
多项式复合集合幂级数
求 。。
组合意义:每次划掉一个包含 的集合,划 次有 的系数。
设 表示当先 要为 ,还剩 次要划的答案。初始 ,转移是不选或子集卷积上 的 。
void xormul1(int *a,int *c,int n){
for(int i=0;i<=n;i++){
for(int s=0;s<(1<<n);s++)ff[i][s]=0;
}
for(int s=0;s<(1<<n);s++)ff[__builtin_popcount(s)][s]=a[s];
for(int i=0;i<=n;i++)fmt1(ff[i],1<<n);
for(int s=0;s<(1<<n);s++){
for(int i=0;i<=n;i++)tf[i]=ff[i][s];
for(int i=0;i<=n;i++)tg[i]=gg[i][s];
for(int i=0;i<=n;i++){
th[i]=0;
for(int j=0;j<=i;j++)inc(th[i],1ll*tf[j]*tg[i-j]%mod);
}
for(int i=0;i<=n;i++)ff[i][s]=th[i];
}
for(int i=0;i<=n;i++)fmt2(ff[i],1<<n);
for(int s=0;s<(1<<n);s++)inc(c[s],ff[__builtin_popcount(s)][s]);
}
int hh[maxn+1][1<<maxn];
void comp(int *a,int *b,int *c,int n){
for(int i=0;i<=n;i++){
for(int j=1;j<=i;j++)b[i]=1ll*b[i]*j%mod;
}
for(int i=0;i<=n;i++)hh[i][0]=b[i];
for(int i=1;i<=n;i++){
for(int j=0;j<i;j++){
for(int s=0;s<(1<<i-1);s++)gg[j][s]=0;
}
for(int s=0;s<(1<<i-1);s++)gg[__builtin_popcount(s)][s]=a[s+(1<<i-1)];
for(int j=0;j<i;j++)fmt1(gg[j],1<<i-1);
for(int j=1;j<=n-i+1;j++){
xormul1(hh[j],hh[j-1]+(1<<i-1),i-1);
}
}
for(int s=0;s<(1<<n);s++)c[s]=hh[0][s];
}在做 ln exp 上不如普通版本。
但更通用,比如 k-exp 之类的题目就不用推 的式子了。
转置
没完,还可以转置。
对每个 求 。
选 个拼起来,正着加 high bit 。倒着删 high bit,设 已经删 次剩 ,每次 。子集差卷积。
void xormul1(int *a,int *c,int n){
for(int i=0;i<=n;i++){
for(int s=0;s<(1<<n);s++)ff[i][s]=0;
}
for(int s=0;s<(1<<n);s++)ff[__builtin_popcount(s)][s]=a[s^((1<<n)-1)];
for(int i=0;i<=n;i++)fmt1(ff[i],1<<n);
for(int s=0;s<(1<<n);s++){
for(int i=0;i<=n;i++)tf[i]=ff[i][s];
for(int i=0;i<=n;i++)tg[i]=gg[i][s];
for(int i=0;i<=n;i++){
th[i]=0;
for(int j=0;j<=i;j++)inc(th[i],1ll*tf[j]*tg[i-j]%mod);
}
for(int i=0;i<=n;i++)ff[i][s]=th[i];
}
for(int i=0;i<=n;i++)fmt2(ff[i],1<<n);
for(int s=0;s<(1<<n);s++)inc(c[s^((1<<n)-1)],ff[__builtin_popcount(s)][s]);
}
int hh[maxn+1][1<<maxn];
void comptrans(int *a,int *b,int *c,int n){
for(int s=0;s<(1<<n);s++)hh[0][(1<<n)-1-s]=b[s];
hh[0][(1<<n)-1]=1;
for(int i=n;i;i--){
for(int j=0;j<i;j++){
for(int s=0;s<(1<<i-1);s++)gg[j][s]=0;
}
for(int s=0;s<(1<<i-1);s++)gg[__builtin_popcount(s)][s]=a[s+(1<<i-1)];
for(int j=0;j<i;j++)fmt1(gg[j],1<<i-1);
for(int j=n-i+1;j;j--){
xormul1(hh[j-1]+(1<<i-1),hh[j],i-1);
}
}
for(int i=0;i<=n;i++)c[i]=hh[i][0];
}