题目
题意概要
在给出的
n
n
n 个自然数中,选出两个集合,其下标不相交(一个元素至多属于一个集合),且两个集合至少有一个非空,使得两个集合内的数字的异或和相等。问方案数,对
998244353
998244353
998244353 取模。
数据范围与提示
n
⩽
1
0
6
n\leqslant 10^6
n⩽106 且给出数字
a
i
∈
[
0
,
1
0
6
]
a_i\in[0,10^6]
ai∈[0,106] 。
思路
以为是线性基。完全不是。
普通的计数题,可以想到 生成函数,跟位运算相关,却想不到 集合幂级数,愚蠢啊!
枚举两个集合的并集,只需要数字异或和为
0
0
0,贡献是
2
∣
S
∣
2^{|S|}
2∣S∣ 。所以实际上答案就是
[
x
0
]
∏
(
2
x
a
i
+
1
)
−
1
[x^0]\prod(2x^{a_i}+1)-1
[x0]∏(2xai+1)−1
这是异或卷积的集合幂级数。类似于
OGF
\textit{OGF}
OGF 手推闭形式,我们可以 手推
FWT
\textit{FWT}
FWT,得
∑
[
1
+
2
×
(
−
1
)
∣
S
and
a
i
∣
]
⋅
x
S
\sum\left[1+2\times(-1)^{|S\operatorname{and}a_i|}\right]\cdot x^S
∑[1+2×(−1)∣Sandai∣]⋅xS
于是只需要对于 S S S 求出 ∣ S and a i ∣ m o d 2 |S\operatorname{and}a_i|\bmod 2 ∣Sandai∣mod2 的分布。可以类似 FWT \textit{FWT} FWT 的推导,随便糊一个式子上去,用 p a i r \tt pair pair 做转移。也可以用一种看似 constructive \text{constructive} constructive 的方法,即二者之和 = n =n =n 已知,所以只需要算二者之差;这恰好是异或卷积的 FWT \textit{FWT} FWT 过程。
所以就这样了呗。最后可以做 FWT \textit{FWT} FWT 逆变换,甚至可以直接手推逆变换 x 0 x^0 x0 的系数。时间复杂度都是 O ( a log a + n ) \mathcal O(a\log a+n) O(aloga+n) 。
代码
#include <cstdio> // JZM yydJUNK!!!
#include <iostream> // XJX yyds!!!
#include <algorithm> // XYX yydLONELY!!!
#include <cstring> // (the STRONG long for LONELINESS)
#include <cctype> // ZXY yydSISTER!!!
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
inline int readint(){
int a = 0, c = getchar(), f = 1;
for(; !isdigit(c); c=getchar())
if(c == '-') f = -f;
for(; isdigit(c); c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
const int MOD = 998244353, inv2 = (MOD+1)>>1;
inline llong qkpow(llong b, int q){
llong a = 1;
for(; q; q>>=1,b=b*b%MOD)
if(q&1) a = a*b%MOD;
return a;
}
inline int modAdd(int x, const int &y){
return ((x += y) >= MOD) ? (x-MOD) : x;
}
void FWT(int a[], int n){
for(int w=1; w!=1<<n; w<<=1)
for(int *p=a; p!=a+(1<<n); p+=w<<1)
for(int *i=p,*j=p+w; i!=p+w; ++i,++j){
const int t = *j; *j = (*i)-t, *i += t;
}
}
const int MAXN = 1<<20;
int a[MAXN];
int main(){
for(int n=readint(); n; --n) ++ a[readint()];
FWT(a,20); int n = a[0], ans = 0;
for(int *i=a; i!=a+MAXN; ++i){
const int even = ((*i)+n)>>1;
const int now = int(qkpow(3,even));
if((n^even)&1) ans = modAdd(ans,MOD-now);
else ans = modAdd(ans,now);
}
ans = int(ans*qkpow(inv2,20)%MOD);
if(ans) printf("%d\n",ans-1);
else puts("998244352"); // -1
return 0;
}