1. 程式人生 > 實用技巧 >[CF1034E]Little C Loves 3 III(不交併卷積FWT講解)

[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;
}