[CF1034E]Little C Loves 3 III(不交并卷积FWT讲解)
题面
http://codeforces.com/contest/1034/problem/E
题解
前置知识
不交并卷积
是可以使用FWT进行优化的一种卷积形式。
解决方案:设\(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进行或卷积后得到。举个例子:
那么就有
注意,这里的x与生成函数中的作用相同,只是用来表示多项式形式的参数。
将\(A,B\)两个多项式数组进行或卷积后得到
然后我们对于每一个C[k],把它中间\(x^{pop{\_}cnt[k]}\)前面的系数拿出来
就得到了不交并卷积的答案!
这一过程的原理是这样的,如果\(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实际上是
取出了\(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为例:
然后令\(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代入后的值
就可以了,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;
}