Evaluation

题目

给出长度为(2^n) ((nle20))的数组(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自动溢出解决,优化常数。

解法

解法一 容斥原理

[egin{aligned} H(a)&=sum_{b|c=a}f[b]*g[c] \ &=sum_{bigcup csubseteq a}f[b]*g[c] - sum _ {bigcup csubsetneq a} f[b]*g[c] \ &=sum_{bsubset a}f[b]* sum_{csubset a}g[c]-sum_{dsubsetneq 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
",ans);
}

解法二 快速沃尔什变换

快速沃尔什变换解决的是这样一类问题:

[{C_i}={ sum} _ {j igoplus k=i} A_j *B_ k ]

其中数组长度为(2^n).
与FFT相似,我们先把系数表达转为点值,相乘后转回来。
先以$igoplus = 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)) \ ]

验证:

[egin{aligned} A=(A_0,A_1) \ Aprime =(A_0,A_0+A_1) \ B=(B_0,B_1) \ Bprime =(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) \ Cprime =Aprime * Bprime=(A_0*B_0,A_0*B_0+A_0*B_1+A_1*B_0+A_1*B_1) \ 若 Cprime =(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
",ans);
}
原文地址:https://www.cnblogs.com/owenyu/p/6724748.html