题目

QOJ 9727

思路

对于查询二进制数的最大值,按照常规思路从高到底逐位考虑,显然是到某一位时,[l,r][l,r] 的元素中只有一个 00 的时候,把它去掉,答案最大。

如果所有的位置都是要么 2\ge 200,要么没有 00,那么无论去掉哪一个数字都没办法使得结果更大。

如果有多个位置只有 1100,去掉低位的答案是不优于去掉高位的答案的,因为 2k>2k1+2k2++202^k>2^{k-1}+2^{k-2}+\cdots+2^0

因此,现在我们就需要对于每一个区间,维护是否只有一个 00

开一个长度 6464int 数组在本题的数据范围是无法接受的,因此我们只能退而求其次,考虑对每一位的状态用几个 0/10/1 的变量维护,这样最终就可以用几个 ull 来维护所有的状态了。

具体地说,我们设计三个变量 f,g,hf,g,h 分别表示当前位有 00 个、11 个、2\ge 200,显然它们是互斥的,即只能有一个人是 11

设目前的节点是 uu,左右儿子分别为 ls,rsls,rs,那么转移:

  1. 对于 ff,显然是 flsf_{ls}frsf_{rs} 同时为 11 时,fu=1f_u=1;否则 fu=0f_u=0
  2. 对于 gg,当 glsg_{ls}grsg_{rs} 只有一个为 11,并且 gx=0g_x=0 的那个人必须 fx=1f_x=1
  3. 对于 hh,只要 hlsh_{ls}hrsh_{rs}11,或者 gls=grs=1g_{ls}=g_{rs}=1 时即可。

上面是对于某一位,由于位运算每一位都是独立的,所以合起来是下面这样:

1
2
3
4
5
6
7
8
struct node {
int f, g, h;
void set(int a) { h = 0; g = ~a; f = a; }
} dat[N<<2];

node operator+(const node& l, const node& r) {
return {l.f & r.f, (l.f ^ r.f) & (l.g ^ r.g), l.h | r.h | (l.g & r.g)};
}

还有一个问题,区间修改按位与该怎么办?

如果看这个式子,那大概是这个题不可能做出来的,需要考虑具体含义

我们先认为是对于某一位的与,如果与 11,显然 f,g,hf,g,h 均不变;如果与 00,那么:

  1. 对于 ff,置为 00
  2. 对于 gg,如果只有一个元素,置为 11;否则为 00
  3. 对于 hh,如果只有一个元素,置为 00;否则为 11

所以这个线段树的修改函数应当是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void spread(int u, int k, int L, int R) {
tag[u] &= k;
dat[u].f &= k;
if (L == R) {
// 1 element
assert(dat[u].h == 0);
dat[u].g |= ~k;
}
else {
// at least 2 elements
dat[u].h |= ~k;
dat[u].g &= k;
}
}

还有一个问题,我们如何找到那个 00 的位置?

我们知道,如果某一位只有一个 00,那么该位的 g=1g=1,否则 g=0g=0

线段树区间查询的时候会查询 O(logn)O(\log n) 个小区间,因为合并之后 g=1g=1,所以一定有且只有一个区间的 g=1g=1

当我们找到这个区间之后,它的两个子区间必然也是有且只有一个子区间的 g=1g=1

如果从高到底第 ii 位是首个 g=1g=1 的位,那么令 k=263ik=2^{63-i} 作为一个 mask,查询会是这样的:

1
2
3
4
5
6
7
8
9
int querypos(int u, int l, int r, int k, int L, int R) {
if (l <= L && R <= r && (dat[u].g & k) == 0) return 0;
if (L == R) return L;
pushdown(u, L, R);
int res = 0;
if (l <= Mid) res += querypos(ls, l, r, k, L, Mid);
if (Mid+1 <= r) res += querypos(rs, l, r, k, Mid+1, R);
return res;
}

注意第一行代码包含了两种情况,要么是划分的 O(logn)O(\log n) 的那些子区间本身 g=0g=0,要么是对 g=1g=1 的区间二分时 g=0g=0,总之这些都应该被忽略掉。

实现

上面重点阐述了一些对于我比较难理解的部分的代码。

还有一个细节,1-1 是二进制位全是 11 的数,所以将它作为一个 and\operatorname{and} 运算的初始元素是合适的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#include <bits/stdc++.h>
using namespace std;

#define int unsigned long long
#define Mid ((L+R) >> 1)
#define ls (u<<1)
#define rs (u<<1|1)
const int N = 1e6+10;

int tag[N<<2];
int a[N];

struct node {
int f, g, h;
void set(int a) {
h = 0;
g = ~a;
f = a;
}
} dat[N<<2];

node operator+(const node& l, const node& r) {
return {l.f & r.f, (l.f ^ r.f) & (l.g ^ r.g), l.h | r.h | (l.g & r.g)};
}

void pushup(int u) {
dat[u] = dat[ls] + dat[rs];
}

void spread(int u, int k, int L, int R) {
tag[u] &= k;
dat[u].f &= k;
if (L == R) {
// 1 element
assert(dat[u].h == 0);
dat[u].g |= ~k;
}
else {
// at least 2 elements
dat[u].h |= ~k;
dat[u].g &= k;
}
}

void pushdown(int u, int L, int R) {
spread(ls, tag[u], L, Mid);
spread(rs, tag[u], Mid+1, R);
tag[u] = -1;
}

void build(int u, int L, int R) {
tag[u] = -1;
if (L == R) return dat[u].set(a[L]), void();
build(ls, L, Mid);
build(rs, Mid+1, R);
pushup(u);
}

void modify(int u, int l, int r, int k, int L, int R) {
if (l <= L && R <= r) return spread(u, k, L, R);
pushdown(u, L, R);
if (l <= Mid) modify(ls, l, r, k, L, Mid);
if (Mid+1 <= r) modify(rs, l, r, k, Mid+1, R);
pushup(u);
}

void modify(int u, int p, int k, int L, int R) {
if (L == R) return dat[u].set(k), void();
pushdown(u, L, R);
if (p <= Mid) modify(ls, p, k, L, Mid);
else modify(rs, p, k, Mid+1, R);
pushup(u);
}

node query(int u, int l, int r, int L, int R) {
if (l > r) return {-1ull, -1ull, -1ull}; // only when op = 3 will this case be entered
if (l <= L && R <= r) return dat[u];
pushdown(u, L, R);
if (l > Mid) return query(rs, l, r, Mid+1, R);
else if (r < Mid+1) return query(ls, l, r, L, Mid);
else return query(ls, l, r, L, Mid) + query(rs, l, r, Mid+1, R);
}

int querypos(int u, int l, int r, int k, int L, int R) {
if (l <= L && R <= r && (dat[u].g & k) == 0) return 0;
if (L == R) return L;
pushdown(u, L, R);
int res = 0;
if (l <= Mid) res += querypos(ls, l, r, k, L, Mid);
if (Mid+1 <= r) res += querypos(rs, l, r, k, Mid+1, R);
return res;
}

signed main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int n, q;
cin >> n >> q;
for (int i = 1; i <= n; i++) cin >> a[i];
build(1, 1, n);
while (q--) {
int op, l, r, x, s;
cin >> op;
if (op == 1) {
cin >> l >> r >> x;
modify(1, l, r, x, 1, n);
}
else if (op == 2) {
cin >> s >> x;
modify(1, s, x, 1, n);
}
else {
cin >> l >> r;
node nd = query(1, l, r, 1, n);
int g = nd.g;
if (g == 0) cout << nd.f << '\n';
else {
int k = 1ull << (63 - __builtin_clzll(nd.g));
int pos = querypos(1, l, r, k, 1, n);
cout << (query(1, l, pos-1, 1, n).f & query(1, pos+1, r, 1, n).f) << '\n';
}
}
}
return 0;
}