题目描述:给定 (n) 个数 (a_iin[0,2^m)),对所有 (k=0,1,dots,m),求 (sum_{Sin {a_i}}[ ext{popcount}(igoplus_{xin S}x)=k])。
数据范围:(nle 2cdot 10^5,mle 53)。部分分 (mle 35)。
本文参考官方题解。
设 (A) 是给出的数得到的线性基,( ext{span}(A)) 表示 (A) 张成的线性空间, (F(S)=sum_{xin ext{span}(S)}z^x),(P(S)=sum_{xin ext{span}(S)}z^{ ext{popcount}(x)})。
设 (langle i,j angle= ext{popcount}(i ext{ and } j))。生成函数乘法默认 xor 卷积。
则 (langle i,k angle+langle j,k angleequiv langle ioplus j,k angle ( ext{mod} 2)),即乘法(and)关于加法(xor)的分配律。
首先大家肯定都会 (O(2^{ ext{rank}(A)})),即枚举线性基的子集。
对于 (mle 35),直接用 meet-in-the-middle,把线性基分成高位和低位两部分,其中低位部分只有在最低 $ lceilfrac{m}{2} ceil$ 位有值。设 (f_{i,S}) 表示对于高位的线性基异或出来的所有值,较高 (lfloorfrac{m}{2} floor) 位的 popcount 为 (i),较低 (lceilfrac{m}{2} ceil) 为 (S) 的方案数。(g_S) 表示低位的线性基异或出 (S) 的方案数。将 (m) 个 (f_i) 与 (g) 做 xor 卷积合并即可。复杂度 (O(m2^{lceilfrac{m}{2} ceil}+m^22^{lfloorfrac{m}{2} floor}))。
结果这个做法对满分做法没有一点用处(
满分做法使用了根号分治的思想,对于 ( ext{rank}(A)le lfloorfrac{m}{2} floor) 时使用暴力,对于其他情况考虑寻找一个 (O(2^{m- ext{rank}(A)})) 的做法。
有线性基的一些性质得到:
第一行:线性空间 做 线性空间内的线性变换 得到它自己。
第二行:乘法分配律。
第三、四行:做 FWT。根据这个柿子就可以知道,$ ext{FWT}(F(A))$ 的每一位是 $0$ 或 $2^{ ext{rank}(A)}$。
第五、六行:如果存在一个 $x$ 使得 $2
ot|langle k,x
angle$,则该位不可能为 $2^{ ext{rank}(A)}$,只能为 $0$。
定义 (A) 的正交线性基为 (B),使得 (forall xin A,yin B),(2|langle x,y angle)。并且 ( ext{rank}(A)+ ext{rank}(B)=m)。
则 (F(B)cdot 2^{ ext{rank}(A)}= ext{FWT}(F(A)))。
至于它怎么求,你可以把线性基中的列进行一些交换,使得它的左边是一个边长 ( ext{rank}(A)) 的单位矩阵。
然后将右边一个矩形对称到它的左下方,右下方是一个边长为 (m- ext{rank}(A)) 的单位矩阵。
(放一个官方题解的图)
很容易检验它就是 (A) 的正交线性基,并且两两之间的 (langle x,y anglein{0,2})。
求出正交线性基之后呢,考虑算答案。设 (G_c=sum_{xge 0}z^x[ ext{popcount}(x)=c])。
我们知道 ( ext{FWT}(F(A))=F(B)cdot 2^{ ext{rank}(A)})。
((d) 为枚举 (F(B)) 中每一项的大小,(i) 是 FWT 时的两个集合的交集大小)
直接计算,时间复杂度 (O(2^{lfloorfrac{m}{2} floor}+mn+m^3))。
总而言之,正交线性基是一个与原线性基“互补”的东西,同时反映了原线性基的性质。也就找到了 (O(2^{m- ext{rank}(A)})) 的做法。
#include<bits/stdc++.h>
#define Rint register int
using namespace std;
typedef long long LL;
const int N = 63, mod = 998244353;
template<typename T>
inline void read(T &x){
int ch = getchar(); x = 0;
for(;ch < '0' || ch > '9';ch = getchar());
for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
}
template<typename T>
inline bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
template<typename T>
inline bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
inline int ksm(int a, int b){
int res = 1; if(b < 0) b += mod - 1;
while(b){
if(b & 1) res = (LL) res * a % mod;
a = (LL) a * a % mod; b >>= 1;
}
return res;
}
inline void qmo(int &x){x += (x >> 31) & mod;}
LL lb[N], a[N], b[N], p[N];
int n, m, k, C[N][N];
inline void insert(LL x){
for(Rint i = m - 1;~i;-- i) if((x >> i) & 1){
if(!lb[i]){lb[i] = x; return;}
x ^= lb[i];
}
}
inline void dfs(int dep, LL x){
if(dep == k){++ p[__builtin_popcountll(x)]; return;}
dfs(dep + 1, x); dfs(dep + 1, x ^ a[dep]);
}
int main(){
read(n); read(m);
for(Rint i = 1;i <= n;++ i){
LL x; read(x); insert(x);
}
for(Rint i = 0;i < m;++ i)
if(lb[i]){
for(Rint j = i - 1;~j;-- j)
if((lb[i] >> j) & 1) lb[i] ^= lb[j];
}
for(Rint i = 0;i < m;++ i)
if(lb[i]){
for(Rint j = 0;j < m;++ j)
if(!lb[j]) a[k] = (a[k] << 1) | (lb[i] >> j & 1);
a[k] |= (1ll << m - 1 - k); ++ k;
}
if(k <= (m >> 1)){
dfs(0, 0); int tmp = ksm(2, n - k);
for(Rint i = 0;i <= m;++ i) printf("%lld ", p[i] * tmp % mod);
} else {
for(Rint i = 0;i < m - k;++ i){
for(Rint j = 0;j < k;++ j)
if((a[j] >> i) & 1)
b[i] |= (1ll << j);
b[i] |= (1ll << m - 1 - i);
}
swap(a, b); k = m - k;
dfs(0, 0); int tmp = ksm(2, n - m);
C[0][0] = 1;
for(Rint i = 1;i <= m;++ i){
C[i][0] = 1;
for(Rint j = 1;j <= i;++ j)
qmo(C[i][j] = C[i - 1][j] + C[i - 1][j - 1] - mod);
}
for(Rint c = 0;c <= m;++ c){
int ans = 0;
for(Rint d = 0;d <= m;++ d){
int tt = 0;
for(Rint i = 0;i <= c;++ i){
int ttt = (LL) C[d][i] * C[m - d][c - i] % mod;
if(i & 1) qmo(tt -= ttt);
else qmo(tt += ttt - mod);
}
qmo(ans += (LL) tt * p[d] % mod - mod);
}
printf("%lld ", (LL) ans * tmp % mod);
}
}
}