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);
}
posted @ 2017-04-17 20:41  permui  阅读(353)  评论(0编辑  收藏  举报