Live2D

CF1616H Keep XOR Low

link

Solution

不难想到先建出一个 01trie 树。

我们首先先考虑如果在一个子树内进行 dp 是否可行,假设当前深度为 \(d\),可以发现当 \(X\)\(d\) 位为 \(1\) 的时候我就可以往两个儿子走,这样我们就考虑不到了。

所以我们设 \(g(u_1,u_2,d)\) 表示从 \(u_1,u_2\) 中选出若干个数使得两两异或和 \(\le X\) (仅考虑在 \(\le d\) 的位)的方案数。可以发现的一个性质的是,当我们 \(u_1\not=u_2\) 的时候,我们如果只选一个子树内的数的话一定是合法的,因为你上面转了一次一后意味着有一位是 \(<X\) 的。

然后我们先考虑 \(u_1=u_2\) 的情况。如果 \(X\) 在当前位为 \(1\),那么答案就是 \(g(ch[u][0],ch[u][1],d-1)\),因为你两边都可以走,而且根据上面的性质,可以发现只选一边的一定可以考虑到。如果当前位为 \(0\),那么答案就是\(g(ch[u][0],ch[u][0],d-1)+g(ch[u][1],ch[u][1],d-1)-1\),因为你只能都往一边走,\(-1\) 是因为两边都算到了空集。

然后再考虑 \(u_1\not=u_2\) 的情况。如果 \(X\) 在当前位为 \(1\),可以发现 \(ch[u_1][0],ch[u_2][1]\) 以及 \(ch[u_1][1],ch[u_2][0]\) 是独立的,因为 \(ch[u_1][0]\)\(ch[u_1][1]\) 一定可以一起选,\(ch[u_2][0]\)\(ch[u_2][1]\) 一定可以一起选。所以答案就是 \(g(ch[u_1][0],ch[u_2][1],d-1)\times g(ch[u_1][1],ch[u_2][0],d-1)\)。如果 \(X\) 在当前位为 \(0\),贡献是 \(g(ch[u_1][0],ch[u_2][0],d-1)+g(ch[u_1][1],ch[u_2][1],d-1)-1\)。但是,我们这个时候还需要考虑只选两边子树的,原因就是那个性质,我们还需要保证每个子树每个 \(ch[u][0/1]\) 都选,不然就会跟上面算重,上面一种情况因为 \(ch[u][0]\)\(ch[u][1]\) 是独立的,所以已经考虑只选两边子树的情况。

复杂度显然 \(\Theta(n\log v)\)

Code

#include <bits/stdc++.h>
using namespace std;

#define Int register int
#define mod 998244353
#define MAXN 150005

template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
template <typename T,typename ... Args> inline void read (T &t,Args&... args){read (t);read (args...);}
template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}
template <typename T> inline void chkmax (T &a,T b){a = max (a,b);}
template <typename T> inline void chkmin (T &a,T b){a = min (a,b);}

int n,K,a[MAXN];
int mul (int a,int b){return 1ll * a * b % mod;}
int dec (int a,int b){return a >= b ? a - b : a + mod - b;}
int add (int a,int b){return a + b >= mod ? a + b - mod : a + b;}
int qkpow (int a,int b){
	int res = 1;for (;b;b >>= 1,a = mul (a,a)) if (b & 1) res = mul (res,a);
	return res;
}
void Add (int &a,int b){a = add (a,b);}
void Sub (int &a,int b){a = dec (a,b);}

#define LOGN 31
int tot = 1,pw[MAXN],siz[MAXN * LOGN],son[MAXN * LOGN][2];

void ins (int val){
	int now = 1;siz[now] ++;
	for (Int i = 30;~i;-- i){
		int k = val >> i & 1;
		if (!son[now][k]) son[now][k] = ++ tot;
		now = son[now][k],siz[now] ++;
	}
}

int query (int u1,int u2,int d){//表示在u1,u2子树里选若干个数使得两两之间的点的异或和<=x的方案数
	// cout << u1 << " , " << u2 << ": " << d << " , " << siz[u1] << endl;
	if (!u1) return pw[siz[u2]];
	if (!u2) return pw[siz[u1]];
	if (u1 == u2){
		if (d < 0) return pw[siz[u1]];
		if (K >> d & 1) return query (son[u1][0],son[u1][1],d - 1);
		else return dec (add (query (son[u1][0],son[u1][0],d - 1),query (son[u1][1],son[u1][1],d - 1)),1);
		//空集算2遍要减1
	}
	else{
		if (d < 0) return pw[siz[u1] + siz[u2]];
		if (K >> d & 1) return mul (query (son[u1][0],son[u2][1],d - 1),query (son[u1][1],son[u2][0],d - 1));
		else{
			int res = dec (add (query (son[u1][0],son[u2][0],d - 1),query (son[u1][1],son[u2][1],d - 1)),1);
			Add (res,mul (dec (pw[siz[son[u1][0]]],1),dec (pw[siz[son[u1][1]]],1)));//只在u1里面选择的方案数,如果里面son[u1][0]或者son[u1][1]空了就会算重
			Add (res,mul (dec (pw[siz[son[u2][0]]],1),dec (pw[siz[son[u2][1]]],1)));//同上
			return res;
		}
	}
}

signed main(){
	read (n,K);
	for (Int x = 1;x <= n;++ x) read (a[x]),ins (a[x]);
	pw[0] = 1;for (Int i = 1;i <= n;++ i) pw[i] = add (pw[i - 1],pw[i - 1]);
	write (dec (query (1,1,30),1)),putchar ('\n');
	return 0;
}
posted @ 2022-10-05 22:49  Dark_Romance  阅读(28)  评论(0编辑  收藏  举报