【JZOJ7038】异或

【JZOJ7038】异或

by AmanoKumiko

Description

原来也有个人对我很好很好,但那个时候我很任性,他走的时候也没有好好说再 见,后来才知道,他再也不会回来了。

给定一个长度为 N 的整数序列 ai ,以及一个整数 x 。 你需要找出序列 {ai} 有多少非空子集,使得子集内的元素两两异或的结果均不小 于 x 。由于答案可能很大,你只需要求出其对 998244353 取模的结果即可。

Input

从文件 inception.in 中读取数据。 输入第一行包含三个整数 Num, N, x ,其中 Num 表示该数据所属的子任务编号。 接下来一行 N 个整数,第 i 个整数为 ai 。

Output

输出到文件 inception.out 中。 输出一行一个整数 Ans ,表示答案对 998244353 取模的结果。

Sample Input

1 3 2 
0 1 2

Sample Output

5

Data Constraint

对于所有测试数据,保证 1 ≤ N ≤ 3 × 105 , 0 ≤ ai , x ≤ 2 60 − 1 。

Solution

结论:一段升序序列的任意两个数的异或值的最小值出现在相邻两个数之间

故排序后dp+trie计数

Code

#include<bits/stdc++.h>
using namespace std;
#define F(i,a,b) for(int i=a;i<=b;i++)
#define Fd(i,a,b) for(int i=a;i>=b;i--)
#define mo 998244353
#define LL long long
#define N 300010
#define M 20000010

int Num,n;
LL X,a[N],ans,mi[60];
struct Trie{
	LL sum[M];
	int ch[M][2],cnt;
	LL query(LL x,LL y,LL z){
		int now=0;LL res=0;
		bool flag=0;
		Fd(i,59,0){
			if(!now&&flag)return res;
			flag=1;
			bool v=mi[i]&z;
			if(!(mi[i]&y))(res+=ch[now][v^1]?sum[ch[now][v^1]]:0)%=mo;
			now=ch[now][v];
		}
		return (res+(now?sum[now]:0))%mo;
	}
	void insert(LL x,LL y){
		int now=0;
		Fd(i,59,0){
			bool v=mi[i]&x;
			(sum[now]+=y)%=mo;
			ch[now][v]?now=ch[now][v]:now=ch[now][v]=++cnt;
		}
		(sum[now]+=y)%=mo;
	}
}t;

int main(){
	freopen("inception.in","r",stdin);
	freopen("inception.out","w",stdout);
	scanf("%d%d%lld",&Num,&n,&X);
	F(i,1,n)scanf("%lld",&a[i]);
	mi[0]=1;F(i,1,59)mi[i]=mi[i-1]*2;
	sort(a+1,a+n+1);
	F(i,1,n){
		LL Q=t.query(a[i],X,a[i]^X)+1;
		(ans+=Q)%=mo;
		t.insert(a[i],Q);
	}
	printf("%lld",ans);
	return 0;
}
posted @ 2021-04-05 21:14  冰雾  阅读(88)  评论(0编辑  收藏  举报