当前位置: 代码迷 >> 综合 >> JZOJ 6829. 【2020.10.25提高组模拟】异或(DP+线段树)
  详细解决方案

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

热度:39   发布时间:2024-03-09 13:10:55.0

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

题目大意

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

题解

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

代码

#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\n", ans);return 0;
}