题目
点这里看题目。
给定一个长度为 \(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;
}
标签:le,bas,int,Chiori,CF1336E,vec,inline,Picking,operatorname
From: https://www.cnblogs.com/crashed/p/17164361.html