「CF1336E」Chiori and Doll Picking

题目

点这里看题目。


给定一个长度为 \(n\) 的非负整数序列 \(a\) 和非负整数参数 \(m\)保证 \(\forall 1\le i\le n,0\le a_i<2^m\)

\(U=\{1,2,3,\dots,n-1,n\}\)。对于非负整数 \(x\),定义 \(|x|\)\(x\) 的二进制表示中 \(1\) 的个数。

你需要对于 \(0\le c\le m\) 的每个整数 \(c\),求出:

\[\sum_{S\subseteq U}\left[\left|\bigoplus_{x\in S}a_x\right|=c\right]\bmod 998244353 \]

Easy Version:所有数据满足 \(1\le n\le 2\times 10^5,0\le m\le 35\)

Hard Version:所有数据满足 \(1\le n\le 2\times 10^5,0\le m\le 53\)

分析

看到这种随便选子集异或的题目,首先把异或线性基建出来,并记序列 \(a\) 的向量张成的空间为 \(A\)

那么,我们只需要对于 \(A\) 中的向量算一遍答案,之后乘上系数 \(2^{n-\operatorname{rank} A}\) 就是我们要的答案。

注意此处异或线性基的形式:因为只需要考虑 \(1\) 的个数,而不需要考虑具体的顺序,所以可以进行列的交换,从而将基简化成 \(\left[\begin{matrix}I_r&C\end{matrix}\right]\) 的形式(其中 \(r=\operatorname{rank} A\)\(C\)\(r\times (n-r)\) 的矩阵)。

Easy Version.

注意到 \(\lceil\frac m 2\rceil\le 18\) 的,并且 \(2^{\frac m 2}\) 是一个比较合理的复杂度因子。

所以考虑 meet-in-middle,当 \(m\le 18\) 的时候直接暴力枚举。当 \(m>18\) 的时候,我们注意到线性基中非主元数量 \(\le 16\),因此每个基向量只需要保留非主元的信息,之后执行异或卷积,额外记录用了多少个基向量即可计算出最终向量中 \(1\) 的个数。

Hard Version.

暴力枚举仍然是可行的,但是限于 \(O(2^{\operatorname{rank} A})\) 的复杂度,暴力最多帮我们做完 \(\operatorname{rank} A\le 27\) 的情况。看着剩下的 \(m-\operatorname{rank}\le 26\),道阻且长!

不妨考虑优化 FWT。设 \(S(\vec x)\) 为向量 \(\vec x\) 对应的集合,考察一个基向量 \(\vec v\) 对应的集合幂级数 \(f=1+x^{S(\vec v)}\) 进行 FWT 之后的结果:\(\operatorname{FWT}(f)_{S(\vec u)}=2\times [\vec u\cdot \vec v\equiv 0\pmod 2]\)。从线性代数的观点看,\([\vec u\cdot \vec v\equiv 0\pmod 2]\) 意味着向量 \(t\) 与向量 \(s\) 正交。将所有基向量的 FWT 变换后的结果乘起来之后,一个向量 \(\vec u\) 的系数便取决于其是否与所有基向量正交,也即是否与 \(A\) 中的所有向量正交。是,则系数为 \(2^{\operatorname{rank} A}\);否,则系数为 \(0\)

\(A\) 的所有向量正交的向量显然也构成一个线性子空间,记为 \(B\)。注意到 \(\operatorname{rank} A+\operatorname{rank} B=m\),这意味着在 \(\operatorname{rank} A\) 较大的时候,枚举 \(B\) 中的向量不会导致复杂度爆炸。

于是,考察 FWT 的逆变换为:

\[[\vec u\in A]=2^{-m}\sum_{\vec v\in B}(-1)^{\vec u\cdot \vec v}2^{\operatorname{rank} A} \]

进行一个枚的举:

\[\begin{aligned} \sum_{|S(\vec u)|=c}[\vec u\in A] &=\frac{1}{2^{m-\operatorname{rank} A}}\sum_{\vec v\in B}\sum_{|S(\vec u)|=c}(-1)^{\vec u\cdot \vec v}\\ &=\frac{1}{2^{m-\operatorname{rank} A}}\sum_{\vec v\in B}\sum_{k=0}^c(-1)^k\binom{|S(\vec v)|}{k}\binom{m-|S(\vec v)|}{c-k} \end{aligned} \]

只需要对于 \(0\le j\le m\),计算出 \(B\) 中包含 \(j\)\(1\) 的向量有多少个即可。

尝试构造一组 \(B\) 中的基。这可能是什么经典的构造,我也没有办法解释,所以我还是直接搬运好了。结论就是:\(\left[\begin{matrix}C^T&I_{n-r}\end{matrix}\right]\) 的行空间即为 \(B\)。验证它的正确性是容易的,此处略去。

于是,本题可以在 \(O(2^{\frac m 2})\) 的时间复杂度内解决。

Remark.

从这个角度来看的话,FWT 似乎在线性代数背景下有很深刻的含义,但是我还没法解释。

不过,从这道题本身,FWT 已经帮我们得到一个线性空间和与它正交的向量之间的关系:

\[[\vec u\perp A]=\frac{1}{2^{\operatorname{rank} A}}\sum_{\vec v\in A} (-1)^{\vec u\cdot \vec v} \]

(记法可能不严谨)

如果已知这样一个关系,再从“补集”的角度出发,也可以得到上面的算法。

代码

#include <cstdio>
#include <algorithm>

#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )

typedef unsigned long long ull;

const int mod = 998244353, inv2 = ( mod + 1 ) >> 1;
const int MAXN = 60;

template<typename _T>
inline void Read( _T &x ) {
	x = 0; char s = getchar(); bool f = false;
	while( ! ( '0' <= s && s <= '9' ) ) { f = s == '-', s = getchar(); }
	while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
	if( f ) x = -x;
}

template<typename _T>
inline void Write( _T x ) {
	if( x < 0 ) putchar( '-' ), x = -x;
	if( 9 < x ) Write( x / 10 );
	putchar( x % 10 + '0' );
}

struct Base {
	ull bas[MAXN];
	int n, m;

	Base(): bas{}, n( 0 ), m( 0 ) {}

	inline void Insert( ull x ) {
		rep( i, 0, n - 1 ) {
			if( ! ( x >> i & 1 ) ) continue;
			if( ! bas[i] ) {
				bas[i] = x;
				break;
			}
			x ^= bas[i];
		}
	}

	inline void Regularize() {
		m = 0;
		rep( i, 0, n - 1 ) {
			if( ! bas[i] ) continue;
			rep( j, 0, n - 1 )
				if( i ^ j && ( bas[j] >> i & 1 ) )
					bas[j] ^= bas[i];
			m ++;
		}
		rep( i, 0, m - 1 ) {
			if( bas[i] ) continue;
			rep( j, i + 1, n - 1 ) if( bas[j] ) {
				std :: swap( bas[i], bas[j] ); break;
			}
		}
		rep( i, 0, m - 1 ) {
			if( bas[i] >> i & 1 ) continue;
			rep( j, i + 1, n - 1 ) {
				if( ! ( bas[i] >> j & 1 ) ) continue;
				ull msk = ( 1llu << i ) ^ ( 1llu << j );
				rep( k, 0, n - 1 ) 
					if( ( bas[k] >> i & 1 ) ^
						( bas[k] >> j & 1 ) )
						bas[k] ^= msk;
				break;
			}
		}
	}
};

Base glb;

int ans[MAXN], pw2[MAXN];

int N, M;

inline int Qkpow( int, int );
inline int Inv( const int &a ) { return Qkpow( a, mod - 2 ); }
inline int Mul( int x, const int &v ) { return 1ll * x * v % mod; }
inline int Sub( int x, const int &v ) { return ( x -= v ) < 0 ? x + mod : x; }
inline int Add( int x, const int &v ) { return ( x += v ) >= mod ? x - mod : x; }

inline int& MulEq( int &x, const int &v ) { return x = 1ll * x * v % mod; }
inline int& SubEq( int &x, const int &v ) { return ( x -= v ) < 0 ? ( x += mod ) : x; }
inline int& AddEq( int &x, const int &v ) { return ( x += v ) >= mod ? ( x -= mod ) : x; }

inline int Qkpow( int base, int indx ) {
	int ret = 1;
	while( indx ) {
		if( indx & 1 ) MulEq( ret, base );
		MulEq( base, base ), indx >>= 1;
	}
	return ret;
}

inline Base Orthogonal( const Base &A ) {
	Base B;
	int n = A.n, m = A.m;
	B.n = n, B.m = n - m;
	rep( i, 0, m - 1 ) rep( j, m, n - 1 )
		if( A.bas[i] >> j & 1 )
			B.bas[j - m] |= 1llu << i;
	rep( i, 0, B.m - 1 )
		B.bas[i] |= 1llu << ( i + m );
	return B;
}

namespace Force {
	ull xorSum[MAXN];

	inline void Solve() {
		ans[0] ++;
		for( int S = 1 ; S < ( 1 << glb.m ) ; S ++ ) {
			int p = __builtin_popcount( S );
			xorSum[p] = xorSum[p - 1] ^ glb.bas[__builtin_ctz( S )];
			ans[__builtin_popcountll( xorSum[p] )] ++;
		}
	}
}

namespace ForceAgain {
	int C[MAXN][MAXN], cnt[MAXN];
	ull xorSum[MAXN]; 

	inline void Solve() {
		Base loc = Orthogonal( glb );
		rep( i, 0, M ) {
			C[i][0] = 1;
			rep( j, 1, i )
				C[i][j] = Add( C[i - 1][j], C[i - 1][j - 1] );
		}
		cnt[0] ++;
		for( int S = 1 ; S < ( 1 << loc.m ) ; S ++ ) {
			int p = __builtin_popcount( S );
			xorSum[p] = xorSum[p - 1] ^ loc.bas[__builtin_ctz( S )];
			cnt[__builtin_popcountll( xorSum[p] )] ++;
		}
		rep( c, 0, M ) {
			rep( y, 0, M ) {
				int coe = 0;
				rep( k, 0, c ) {
					int res = Mul( C[y][k], C[M - y][c - k] );
					k & 1 ? SubEq( coe, res ) : AddEq( coe, res );
				}
				AddEq( ans[c], Mul( cnt[y], coe ) );
			}
			MulEq( ans[c], Qkpow( inv2, M - glb.m ) );
		}
	}
}

int main() {
	Read( N ), Read( M ), glb.n = M;
	rep( i, 1, N ) {
		ull a; Read( a );
		glb.Insert( a );
	}
	glb.Regularize();
	if( glb.m <= 27 ) Force :: Solve();
	else ForceAgain :: Solve();
	int coe = Qkpow( 2, N - glb.m );
	rep( i, 0, M ) Write( Mul( ans[i], coe ) ), putchar( " \n"[i == M] );
	return 0;
}
posted @ 2023-02-28 15:09  crashed  阅读(36)  评论(0编辑  收藏  举报