[CF1034E]Little C Loves 3 III(不交并卷积FWT讲解)

题面

http://codeforces.com/contest/1034/problem/E

题解

前置知识

不交并卷积

是可以使用FWT进行优化的一种卷积形式。

\[c{[}k{]}=\sum\limits_{i|j = x,i{\&}j=0}a[i]b[j] \]

解决方案:设\(A,B,C\)是三个多项式数组,它们的每一项是一个多项式。其中\(A[i]=a[i] x^{pop{\_}cnt[i]}\)\(B[i]=b[i]x^{pop{\_}cnt[i]}\)。(pop_cnt[i]是二进制表示下i中1的个数),而C数组是A与B进行或卷积后得到。举个例子:

\[a[0]=0,a[1]=1,a[2]=2,a[3]=3 \]

\[b[0]=3,b[1]=2,b[2]=1,b[3]=0 \]

那么就有

\[A[0]=0,A[1]=x,A[2]=2x,A[3]=3x^2 \]

\[B[0]=3,B[1]=2x,B[2]=x,B[3]=0x^2 \]

注意,这里的x与生成函数中的作用相同,只是用来表示多项式形式的参数。

\(A,B\)两个多项式数组进行或卷积后得到

\[C[0]=0,C[1]=3x+2x^2,C[2]=6x+2x^2,C[3]=14x^2+9x^3 \]

然后我们对于每一个C[k],把它中间\(x^{pop{\_}cnt[k]}\)前面的系数拿出来

\[c[0]=0,c[1]=3,c[2]=6,c[3]=14 \]

就得到了不交并卷积的答案!

这一过程的原理是这样的,如果\(i|j=k\),那么一定有\(pop{\_}cnt[i]+pop{\_}cnt[j]{\geq}pop{\_}cnt[k]\),并且等号iff i&j=0。这很好理解,因为如果\(i{\&}j{\neq}0\),那么\(i{\&}j\)的每一位上的1都被重复计算了两遍。

我们计算出来的C实际上是

\[C[k]=\sum\limits_{i|j=k}A[i]B[j] \]

\[=\sum\limits_{i|j=k}a[i]b[j]x^{pop{\_}cnt[i]+pop{\_}cnt[j]} \]

取出了\(x^{pop{\_}cnt[k]}\)之前的系数,就自然得到了\(\sum_{i|j=k,i{\&}j=0}a[i]b[j]\)啦。

这样的时间复杂度,假设a,b的长度是\(2^n\),FWT时原本是两个数相加减,现在变成了两个长度\(O(n)\)的多项式相加。只有这里有别,所以总时间复杂度是\(O(n^22^n)\)

回原题

虽然本题求的就是不交并卷积,看上去像模板题,可是\(O(n^22^n)\)的复杂度竟然过不去!这是因为此题的特殊性——要求对4取模,导致可以重新优化掉一个n。

在前面的过程中,我们把x=4代入,仍以样例2为例:

\[a[0]=0,a[1]=1,a[2]=2,a[3]=3 \]

\[b[0]=3,b[1]=2,b[2]=1,b[3]=0 \]

\[A[0]=0,A[1]=x=4,A[2]=2x=8,A[3]=3x^2=48 \]

\[B[0]=3,B[1]=2x=8,B[2]=x=4,B[3]=0x^2=0 \]

\[C[0]=0,C[1]=3x+2x^2=44,C[2]=6x+2x^2=56,C[3]=14x^2+9x^3=800 \]

然后令\(c[k]=C[k] \div 4^{pop{\_}cnt[k]}\),得到

\(c[0]=0,c[1]=3+2x=11,c[2]=6+2x=14,c[3]=14+9x=50\)

最后分别\(\mod 4\),就可以得到答案{0,3,2,2}了。

由于我也保留了未代入前的式子,所以这么做的原理不难理解,我们想要求出C[k]中的\(x^{pop{\_}cnt[k]}\)前的系数\(\mod 4\)的值,就直接把C[k]除以\(4^{pop{\_}cnt[k]}\)再直接\(\mod 4\),次数更高的项仍然能被4整除,在mod的时候就不产生影响了。

这么做,就可以不用构造多项式数组,直接存x=4代入后的值

\[A[0]=0,A[1]=4,A[2]=8,A[3]=48 \]

\[B[0]=3,B[1]=8,B[2]=4,B[3]=0 \]

就可以了,FWT时还是两个数相加减,复杂度相应地回到了\(O(n2^n)\)

过程中,A,B进行或卷积得到的系数可能会很大,为了防止爆掉,采取一种措施——将所有的系数在\(\mod 4^{22}\)意义下进行。由于最终最多需要求系数\(\div 4^{21}\)以后对4取模的值,所以这样仍能保证答案的正确。

代码

#include<bits/stdc++.h>

using namespace std;

#define ll long long
#define ld long double
#define rg register
#define In inline

const ll N = 2097152;
const ll mod = 1ll << 44;

namespace ModCalc{
	In void Inc(ll &x,ll y){
		x += y;if(x >= mod)x -= mod;
	}
	In void Dec(ll &x,ll y){
		x -= y;if(x < 0)x += mod;
	}
	In ll Add(ll x,ll y){
		Inc(x,y);return x;
	}
	In ll Sub(ll x,ll y){
		Dec(x,y);return x;
	}
	In void Adjust(ll &x){
		x = (x % mod + mod) % mod;
	}
	In void Tms(ll &x,ll y){
		x = (ll)x * y - (ll)((ld)x * y / mod) * mod;
		Adjust(x);
	}
	In ll Mul(ll x,ll y){
		Tms(x,y);return x;
	}
};
using namespace ModCalc;

ll n,deg;
char s[N+5];
ll a[N+5],b[N+5];

In void calc(ll &x,ll &y,ll opt){
	if(opt == 1)Inc(y,x);
	else Dec(y,x);
}

In void FWT(ll a[],ll deg,ll opt){
	for(rg int n = 2;n <= deg;n <<= 1){
		int m = n >> 1;
		for(rg int i = 0;i < deg;i += n)
			for(rg int j = 0;j < m;j++)calc(a[i+j],a[i+j+m],opt);
	}
}

ll popc[N+5];

int main(){
	scanf("%lld",&n);
	deg = 1ll << n;
	for(rg int i = 1;i < deg;i++)popc[i] = popc[i>>1] + (i&1);
	scanf("%s",s);
	for(rg int i = 0;i < deg;i++)a[i] = Mul(s[i] - '0',1ll << (popc[i]<<1));
	scanf("%s",s);
	for(rg int i = 0;i < deg;i++)b[i] = Mul(s[i] - '0',1ll << (popc[i]<<1));
	FWT(a,deg,1);
	FWT(b,deg,1);
	for(rg int i = 0;i < deg;i++)Tms(a[i],b[i]);
	FWT(a,deg,-1);
	for(rg int i = 0;i < deg;i++)a[i] >>= (popc[i] << 1),a[i] &= 3;
	for(rg int i = 0;i < deg;i++)putchar(a[i] + '0');putchar('\n');
	return 0;
}
posted @ 2020-10-04 19:50  coder66  阅读(200)  评论(0编辑  收藏  举报