[UNR#2]黎明前的巧克力

280 篇文章 1 订阅

题目

传送门 to UOJ

题意概要
在给出的 n n n 个自然数中,选出两个集合,其下标不相交(一个元素至多属于一个集合),且两个集合至少有一个非空,使得两个集合内的数字的异或和相等。问方案数,对 998244353 998244353 998244353 取模。

数据范围与提示
n ⩽ 1 0 6 n\leqslant 10^6 n106 且给出数字 a i ∈ [ 0 , 1 0 6 ] a_i\in[0,10^6] ai[0,106]

思路

以为是线性基。完全不是。

普通的计数题,可以想到 生成函数,跟位运算相关,却想不到 集合幂级数,愚蠢啊!

枚举两个集合的并集,只需要数字异或和为 0 0 0,贡献是 2 ∣ S ∣ 2^{|S|} 2S 。所以实际上答案就是
[ 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 Sandaimod2 的分布。可以类似 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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值