Evaluation
题目
给出长度为\(2^n\) \((n\le20)\)的数组\(f\)和\(g\),求 \(\sum H(a)*(a+1) \ mod \ 2^{32}\)数组,定义为
\[H(a)={ \sum}_{b|c=a}f[b]*g[c]
\]
分析
好题,学到了很多东西。
看懂题之后就想到zwl之前学过的FWT,即快速沃尔什变换,比赛的时候不会写。于是就开始想优化枚举的方法。暴力直接枚举\(b\)和\(c\)是 \(O(2^{2n})\) 的,肯定会超时,不过最后没有想出更好的方法。本来预计是65左右的,结果只有35,原因是long long不知道怎么没有取好模。这里学到了,这种\(q=2^{32}\)的取模可以直接用unsigned int自动溢出解决,优化常数。
解法
解法一 容斥原理
\[\begin{aligned}
H(a)&=\sum_{b|c=a}f[b]*g[c] \\\
&=\sum_{b\bigcup c\subseteq a}f[b]*g[c] - \sum\ _ {b\bigcup c\subsetneq a} f[b]*g[c] \\\
&=\sum_{b\subset a}f[b]* \sum_{c\subset a}g[c]-\sum_{d\subsetneq a}H(d)
\end{aligned}
\]
于是问题转化成了求\(f\)和\(g\)的子集前缀和,即所有子集的\(f\)和\(g\)的和。
所以我们从小到大枚举所有集合,后面枚举到的集合的子集都已经算好了,所以加上自己就好。最后同样从小到大枚举集合,后面枚举到的\(H\)减去前面的,即减去真子集。这里的或运算转化很漂亮,代码也很简洁优雅。
代码
#include<cstdio>
#include<cctype>
#include<algorithm>
using namespace std;
typedef unsigned int giant;
giant read() {
giant x=0,f=1;
register char c=getchar();
for (;!isdigit(c);c=getchar()) if (c=='-') f=-1;
for (;isdigit(c);c=getchar()) x=x*10+c-'0';
return x*f;
}
const giant maxn=(1<<20)+100;
giant f[maxn],g[maxn],h[maxn];
int main() {
#ifndef ONLINE_JUDGE
freopen("test.in","r",stdin);
freopen("my.out","w",stdout);
#endif
giant n=read();
giant s=1<<n;
for (register giant i=0;i<s;++i) f[i]=read();
for (register giant i=0;i<s;++i) g[i]=read();
for (register giant j=1;j<s;j<<=1) for (int i=0;i<s;++i) if (i&j) f[i]+=f[j^i],g[i]+=g[j^i];
for (register giant i=0;i<s;++i) h[i]=f[i]*g[i];
for (register giant j=1;j<s;j<<=1) for (int i=0;i<s;++i) if (i&j) h[i]-=h[i^j];
giant ans=0;
for (register giant i=0;i<s;++i) ans+=h[i]*(i+1);
printf("%u\n",ans);
}
解法二 快速沃尔什变换
快速沃尔什变换解决的是这样一类问题:
\[{C_i}={ \sum} _ {j \bigoplus k=i} A_j *B_ k
\]
其中数组长度为\(2^n\).
与FFT相似,我们先把系数表达转为点值,相乘后转回来。
先以$\bigoplus = or $为例。
我们可以把一个数组拆分为相同长度的两段:
\[A=(A_0,A_1)
\]
其中
\[A_0=A[0....2^{n-1}],A_1=A[2^{n-1}+1...2^n-1]
\]
即二进制最高位为0或1.
若\(i\)的第\(n\)位为\(0\),则有两种可能:
\[0\ or\ 0 = 0
\]
若\(i\)的第\(n\)位为\(1\),则有两种可能:
\[0\ or\ 1 = 1 \\
1\ or\ 0 = 1 \\
1\ or \ 1=1
\]
能够产生1的放后面,产生0的放前面,通过位运算的性质,我们可推出or运算的转换方法,相乘之后推一下是对的:
\[For=(For(A_0),For(A_0+A_1)) \\
Ior=(Ior(A_0),Ior(A_1-A_0)) \\
\]
验证:
\[\begin{aligned}
A=(A_0,A_1) \\
A\prime =(A_0,A_0+A_1) \\
B=(B_0,B_1) \\
B\prime =(B_0,B_0+B_1) \\
C=A*B=(A_0*B_0,A_0*B_1+A_1*B_0+A_1*B_1) \\
C\prime =A\prime * B\prime=(A_0*B_0,A_0*B_0+A_0*B_1+A_1*B_0+A_1*B_1) \\
若 \ C\prime =(D,E),则观察发现C=(D,E-D),与上面的公式相符。
\end{aligned}
\]
其他的转换方法:
\[Fand=(Fand(A_0+A_1),Fand(A_1)) \\
Iand=(Iand(A_1-A_0),Iand(A_1)) \\
Fxor=(Fxor(A_0+A_1),Fxor(A_0-A_1)) \\
Ixor=(Ixor(\frac{A_0+A_1}{2}),Ixor(\frac{A_0-A_1}{2}))
\]
所以这道题是模板题,写法和\(fft\)差不多,不需要\(ntt\)的原根或者复数根之类的,只是类似模拟一下过程而已。
代码
#include<cstdio>
#include<cctype>
#include<algorithm>
using namespace std;
typedef unsigned int giant;
giant read() {
giant x=0,f=1;
char c=getchar();
for (;!isdigit(c);c=getchar()) if (c=='-') f=-1;
for (;isdigit(c);c=getchar()) x=x*10+c-'0';
return x*f;
}
const giant maxn=(1<<20)+1;
giant a[maxn],b[maxn],n,m,f[maxn];
void F(giant a[],bool inv=false) {
for (register giant i=0;i<m;++i) if (i<f[i]) swap(a[i],a[f[i]]);
for (register giant i=2;i<=m;i<<=1) {
for (register giant j=0;j<m;j+=i) {
for (register giant k=j;k<j+i/2;++k) {
giant u=a[k],v=a[k+i/2];
a[k+i/2]=(inv?v-u:u+v);
}
}
}
}
int main() {
#ifndef ONLINE_JUDGE
freopen("test.in","r",stdin);
#endif
m=1<<(n=read());
for (register giant i=0;i<m;++i) f[i]=(f[i>>1]>>1)|((i&1)<<(n-1));
for (register giant i=0;i<m;++i) a[i]=read();
for (register giant i=0;i<m;++i) b[i]=read();
F(a);
F(b);
for (register giant i=0;i<m;++i) a[i]*=b[i];
F(a,true);
giant ans=0;
for (register giant i=0;i<m;++i) ans+=a[i]*(i+1);
printf("%u\n",ans);
}