CF1408I


CF1408I

給定 \(n,k,c\),以及長度為 \(n\) 的序列 \(a\)(保證元素互不相同)。

操作 \(k\) 次,每次隨機選擇一個 \(a_i\),然后將其 \(-1\)

對於 \(x=0,1~...~2^c-1\) 輸出最后序列的異或和為 \(x\) 的概率。

答案對 \(998244353\) 取模。

\(k,c\le 16,a_i\in [k,2^c),n\le 2^c-k\)

Solution

觀察到一個強有力的性質:

  • 考慮形如 \((x\oplus (x-1),x\oplus (x-2)...x\oplus (x-k))\) 這樣的 \(k\) 元組,在 \(x<2^c\) 的時候,級別近似於 \(\mathcal O(ck)\)

\(c=16,k=16\) 時搜出來的結果為 \(192\)

我們考慮論證:

觀察到 \(x\oplus (x-1)\) 的取值僅有 \(2^t-1\) 的形式。

其中,其取 \(2^t-1\) 表示最低位的 \(1\) 位於 \(t-1\) 位。

\(t> \log k\),那么不難發現 \(x\oplus (x-2)...x\oplus (x-k)\) 均固定了。

否則 \(t\le \log k\),那么后續答案只和其位於 \(\log k\) 位往上走的第一位 \(1\) 所處的位置,和初始后續 \(\log k\) 個位置的具體取值相關,這樣可以產生的不同的 \(k\) 元組的數量為 \(c\times 2^{\log k}=ck\) 這一級別 。

所以本質不同的 \(k\) 元組僅有 \(\mathcal O(ck)\) 種。

考慮令 \(t=a_1\oplus a_2....\oplus a_n\)

那么我們可以考慮答案在 \(t\) 上的改變量。

從計數的角度考慮,我們使用 EGF 來統計操作序列的數量,不妨令 \(d_{i,j}=a_i\oplus (a_i-j)\),不難發現我們要求的即:

\[\prod_i^n \bigg(1+\sum_j^k\frac{1}{j!}\cdot x^{d_{i,j}}y^j\bigg)[y^k]\times k! \]

其中,作用在 \(x\) 維度上的卷積為異或卷積,\(y\) 上則為加法卷積。

於是我們得到了一個暴力做法:

枚舉這 \(ck\)\(k\) 元組,對其中每個元素固定 \(y\) 然后做 \(FWT_{\textrm{xor}}\) 變換,然后枚舉 \(x^{z}\),對於某個 \(z\),答案的貢獻可以描述為 \(ck\) 個關於 \(y\) 的多項式的若干次冪的乘積。取其 \(y^k\) 項即可。

當然,由於固定 \(y\) 和枚舉多項式之后,每個需要進行 FWT 的元素均為單項式 \(x^{d_{i,j}}\),所以我們可以暴力轉 FWT

這樣直接枚舉這 \(ck\) 個多項式,我們發現我們的任務是計算 \(F(y)^m\),於是直接在模 \(y^{k+1}\) 意義下進行暴力的 \(\ln\)\(\exp\) 變換(均為 \(k^2\)),設 \(c,k\) 平級,復雜度為 \(\mathcal O(c^4\cdot 2^c)\),但是 \(\ln\)\(\exp\) 的巨大常數使人無法通過此題。(反正我不行)

我們能否做到更優呢?當然可以:

觀察到 \(FWT_{\textrm{xor}}(j)\) 為:

\[\sum_i (-1)^{\textrm{popcount}(i\& j)} \]

對於某個 \(z\) 而言,我們原本想要計算 \(ck\) 個多項式的若干次冪卷積的結果。

但是實際上,對於 \(z\) 而言,\(k\) 元組 \((a_1,a_2...a_k)\) FWT 的結果等效於一個 \(1/-1\) 的序列 \(((-1)^{a_1\&z},....)\) 的結果,這樣的 1/-1 的序列可以狀壓下來。

於是兩個 \(k\) 元組如果得到的 \(1/-1\) 序列相同,那么 FWT 后得到的多項式將相同,於是他們可以壓起來一起 \(\ln/\exp\) 從而減少運算次數。

原本大概是 \(\mathcal O(c^2\cdot 2^c)\) 級別的運算量,但是加入此優化后需要計算的次數只有 \(1.4\cdot 10^6\) 級別了,那么直接 \(\exp,\ln\) 和暴力卷積即可。

復雜度大概當作 \(\mathcal O(c^3\cdot 2^c)\) 吧?

優雅 の \(\exp\)

\[B(z)=\exp A(z) \]

\[[z^n]B'(z)=[z^n](A'(z)B(z)) \]

\[(n+1)[z^{n+1}]B(z)=\sum_{k} (k+1)[z^{k+1}]A(z)\times B(z)[z^{n-k}] \]

\[[z^{n+1}]B(z)=\frac{1}{n+1}\sum_{k} (k+1)[z^{k+1}]A(z)\times B(z)[z^{n-k}] \]

暴力即可。

優雅 の \(\ln\)

\[B(z)=\ln A(z) \]

\[B(z)'=\frac{A'(z)}{A(z)} \]

\[B(z)'A(z)=A'(z) \]

\[[z^{n+1}]A(z)(n+1)=\sum_{i+j=n}[z^{i+1}]B(z)(i+1)[z^j]A(z) \]

\[A_{n+1}(n+1)=\sum_{i=1}^{n+1}B_{i}\times i\times A_{n+1-i} \]

\[A_n\times n=\sum_{i=1}^n B_i\times i\times A_{n-i} \]

\[B_n=A_n-\frac{1}{n}\sum_{i=1}^{n-1} B_i\times i\times A_{n-i} \]

然后由於我太菜了,所以常數巨大,最后預處理了 \(\ln\) 才過的這個題.../kk

\(Code:\)


#pragma GCC target("sse,sse2,sse3,ssse3,sse4.1,sse4.2,avx,avx2,popcnt,tune=native")
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC optimize( \
	"inline,-fgcse,-fgcse-lm,-fipa-sra,-ftree-pre,-ftree-vrp,-fpeephole2,-ffast-math,-fsched-spec,unroll-loops,-falign-jumps,-falign-loops,-falign-labels,-fdevirtualize,-fcaller-saves,-fcrossjumping,-fthread-jumps,-funroll-loops,-freorder-blocks,-fschedule-insns,inline-functions,-ftree-tail-merge,-fschedule-insns2,-fstrict-aliasing,-fstrict-overflow,-falign-functions,-fcse-follow-jumps,-fsched-interblock,-fpartial-inlining,no-stack-protector,-freorder-functions,-findirect-inlining,-fhoist-adjacent-loads,-frerun-cse-after-loop,inline-small-functions,-finline-small-functions,-ftree-switch-conversion,-foptimize-sibling-calls,-fexpensive-optimizations,inline-functions-called-once,-fdelete-null-pointer-checks")
#include<bits/stdc++.h>
using namespace std ;
#define rep( i, s, t ) for( register int i = (s); i <= (t); ++ i )
#define drep( i, s, t ) for( register int i = (t); i >= (s); -- i )
#define re register
#define int long long
#define pb push_back
#define vi vector<int>
int gi() {
	char cc = getchar() ; int cn = 0, flus = 1 ;
	while( cc < '0' || cc > '9' ) {  if( cc == '-' ) flus = - flus ; cc = getchar() ; }
	while( cc >= '0' && cc <= '9' )  cn = cn * 10 + cc - '0', cc = getchar() ;
	return cn * flus ;
}
const int P = 998244353 ; 
const int N = (1 << 16) + 5 ; 
const int M = 20 + 5 ;
int fpow(int x, int k) {
	int ans = 1, base = x ;
	while(k) {
		if(k & 1) ans = 1ll * ans * base % P ;
		base = 1ll * base * base % P, k >>= 1 ;
	} return ans ;
}
int n, K, m, lim, S, num, f[N], cnt[N], g[M], A[M], fac[M], inv[M], iv[M], st[N], top ; 
int Ln[N][M] ; 
map<vi, int> G ; 
vector<int> sd[205] ; int sc[205] ; 
int bit(int x) { return __builtin_popcount(x) ;}
int Fm[M], Fp[M] ; 
int mod(int x) { return (x - x / P * P) ; }
void polydiv(int *a) { rep( i, 1, K ) a[i - 1] = i * a[i] % P ; a[K] = 0 ; }
void polyln(int *a) {
	rep( j, 0, K ) Fp[j] = 0 ; 
	rep( j, 1, K ) {
		int de = 0 ;
		for(re int i = 1; i < j; ++ i) de = (de + mod(Fp[i] * a[j - i]) * i) ;
		Fp[j] = (a[j] - de % P * iv[j] % P + P) % P ; 
	} rep( j, 0, K ) a[j] = Fp[j] ; 
}
void polyexp(int *a) {
	rep( j, 0, K ) Fp[j] = 0 ; polydiv(a), Fp[0] = 1 ; 
	rep( j, 1, K ) {
		for(int k = 0; k < j; ++ k) Fp[j] = (Fp[j] + mod(Fp[k] * a[j - k - 1])) ; 
		Fp[j] = Fp[j] % P * iv[j] % P ; 
	} rep( j, 0, K ) a[j] = Fp[j] ; 
}
signed main()
{
	n = gi(), K = gi(), m = gi(), lim = 1 << m ; 
	for(int i = 1; i <= n; ++ i) {
		int x = gi(); S ^= x ; vi d ; 
		rep( j, 1, K ) d.pb(x ^ (x - j)) ; ++ G[d] ;
	}
	fac[0] = inv[0] = iv[0] = 1 ; 
	rep( i, 1, K ) 
		fac[i] = fac[i - 1] * i % P, inv[i] = fpow( fac[i], P - 2 ),
		iv[i] = fpow( i, P - 2 ) ; 
	for(auto x : G) ++ num, sd[num] = x.first, sc[num] = x.second ; 
	int St = (1 << K) ; 
	for(re int i = 0; i < St; ++ i) {
		A[0] = 1 ; 
		for(re int j = 1; j <= K; ++ j) 
		A[j] = ((i >> (j - 1)) & 1) ? P - inv[j] : inv[j] ; 
		polyln(A) ; 
		rep( j, 0, K ) Ln[i][j] = A[j] ; 
	}
	for(int t = 0; t < lim; ++ t) {
		for(int i = 1; i <= num; ++ i ) {
			int c = sc[i], z = 0 ; 
			for(re int j = 0; j < K; ++ j) 
				if( bit(sd[i][j] & t) & 1 ) z |= (1 << j) ; //0 mean 1, 1 mean -1
			if( !cnt[z] ) st[++ top] = z ; cnt[z] += c ;
		}
		rep( j, 0, K ) g[j] = 0 ; g[0] = 1 ; 
		for(int x = 1; x <= top; ++ x) {
			int z = st[x] ; 
			rep( j, 0, K ) A[j] = Ln[z][j] * cnt[z] % P ; 
			polyexp(A) ; 
			for(re int j = K; j >= 0; -- j) {
				int tmp = 0 ; 
				rep( k, 0, j ) tmp = (tmp + mod(g[k] * A[j - k])) ; 
				g[j] = tmp % P ; 
			}
		}
		f[t] = g[K] * fac[K] % P ; rep( i, 1, top ) cnt[st[i]] = 0 ; top = 0 ; 
	}
	for(int k = 1; k < lim; k <<= 1) {
		for(int i = 0; i < lim; i += (k << 1)) 
		for(int j = i; j < i + k; ++ j) {
			int nx = f[j], ny = f[j + k] ;
			f[j] = (nx + ny) % P, f[j + k] = (nx - ny + P) % P ;  
		}
	} 
	int di = fpow(lim, P - 2) * fpow(n, P - 1 - K) % P ; 
	for(re int i = 0; i < lim; ++ i) printf("%lld ", f[i ^ S] * di % P ) ; 
	return 0 ;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM