JZOJ 6829. 【2020.10.25提高组模拟】异或(DP+线段树)

JZOJ 6829. 【2020.10.25提高组模拟】异或

题目大意

  • 给出一个长度为 N N N的序列 A A A,求元素两两异或值不小于 X X X的非空子序列个数。
  • N ≤ 300000 , 0 ≤ A i , X < 2 60 Nleq300000,0leq A_i,X< 2^{60} N3000000Ai,X<260.

题解

  • 一开始看到“子序列”困扰了很久,但会发现,题目又要求子序列两两满足某种要求,所以就和序列顺序无关了,那么可以把“子序列”看做是“子集”,
  • 什么样的子集满足条件呢?能否简化题目限制?
  • 结论是两两异或最小值一定会出现在大小每相邻两数异或值当中,怎么证明?
  • 任意递增的三个数,按二进制拆位来看后,总有某一位上的三个数不全相等,
  • 满足三个数不全相等且最高的一位所产生的贡献,足以覆盖过其他位的贡献,所以只用考虑这一位,
  • 只有 0 , 0 , 1 0,0,1 0,0,1 0 , 1 , 1 0,1,1 0,1,1两种情况,而显然它们都满足上面的结论。
  • 那么可以把 A A A排序后,设 f i f_i fi为选到第 i i i个数且选了第 i i i个数的方案数,则 f i = 1 + ∑ A i ⨁ A j ≥ X f j f_i=1+sum_{A_iigoplus A_jgeq X}f_j fi=1+AiAjXfj,然而这样复杂度显然过不去。
  • 考虑用数据结构优化 A i ⨁ A j ≥ X A_iigoplus A_jgeq X AiAjX的判断,可以把每个 f i f_i fi挂在线段树上,这里注意线段树的区间必须是 [ 0 , 2 k ) [0,2^k) [0,2k),这样左右儿子可以分别对应某一位的 0 / 1 0/1 0/1
  • 插入就直接插入到对应的位置,
  • 查询的话,每到一位上,如果 X X X的这一位为 1 1 1,那异或后是 0 0 0的子树上就都不满足,直接递归另一边;如果 X X X的这一位为 0 0 0,那异或后是 1 1 1的子树上就都满足,可以直接加上,也只用递归另一边。

代码

#include<cstdio> 
#include<cstring>
#include<algorithm>
using namespace std;
#define ll long long
#define N 300010
#define md 998244353
ll X, a[N], F[N];
int len = 1;
struct {
	ll s = 0;
	int l = 0, r = 0;
}f[N * 60];
ll find(int v, ll t, ll x, ll s) {
	if(s >= X) return f[v].s;
	ll sum = 0;
	if(f[v].l && ((X & t) == 0 || (x & t) != 0)) (sum += find(f[v].l, t / 2, x, s + ((x & t) ^ 0))) %= md;
	if(f[v].r && ((X & t) == 0 || ((x & t) ^ t) != 0)) (sum += find(f[v].r, t / 2, x, s + ((x & t) ^ t))) %= md;
	return sum;
}
void add(int v, ll t, ll x, ll c) {
	if(t == 0) (f[v].s += c) %= md;
	else {
		if(x & t) {
			if(!f[v].r) f[v].r = ++len;
			add(f[v].r, t / 2, x, c);
		}
		else {
			if(!f[v].l) f[v].l = ++len;
			add(f[v].l, t / 2, x, c);
		}
		f[v].s = 0;
		if(f[v].l) (f[v].s += f[f[v].l].s) %= md;
		if(f[v].r) (f[v].s += f[f[v].r].s) %= md;
	}
}
int main() {
	int n, i;
	scanf("%d%lld", &n, &X);
	for(i = 1; i <= n; i++) scanf("%lld", &a[i]);
	sort(a + 1, a + n + 1);
	ll ans = 0;
	for(i = 1; i <= n; i++) {
		F[i] = find(1, (1ll << 59), a[i], 0) + 1;
		add(1, (1ll << 59), a[i], F[i]);
		(ans += F[i]) %= md;
	}
	printf("%lld
", ans);
	return 0;
}
哈哈哈哈哈哈哈哈哈哈
原文地址:https://www.cnblogs.com/LZA119/p/13910013.html