0-1 思维学习笔记

  1. 1. 1. 树状数组
  2. 2. 2. 状态压缩
  3. 3. 3. 01-Trie
  4. 4. 题目描述
    1. 4.1. 简化题面
  5. 5. 输入格式
  6. 6. 输出格式
    1. 6.1. Trie 应用题
  7. 7. P4551 最长异或路径

二进制思想在信息学的实践,是无比重要的。

1. 树状数组

使用了民用计算机补码的性质,使树状数组移动的常数小且空间小,且易实现,代码简单。

总的来说,树状数组应用了线段树维护区间的思想,将区间二分为块,每次修改和查询只需要修改 个点。

只是,树状数组实际上维护了一个二分的前缀和数组,而并不是维护区间。

在查询时使用前缀和的性质求解。

所以树状数组只能维护有结合律的运算。

在树结构上与众不同,使用了二进制的特性。

树状数组的节点编号是树状数组的精髓,如下图

编号从

假设要查询 ,从编号 开始。

计算 ,那么如何计算 呢?

向左上跳,跳到 。加起来,就是结果。

事实上,树状数组的树形态是 连边得到的图,其中 ​ 的父亲。(边是修饰,实际上不需要连边,这也是树状数组的一大特色:简洁)

树状数组运用了位运算的性质,可以方便地求出当前 的范围,不重复地计算前缀和。

单点修改,只需要像查询一样,不过要向右跳,为什么?

因为部分前缀和 只向前统计,修改了 只会影响到

有了 ,树状数组变成了这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int lowbit(x) { return x & -x; }
int getsum(int x) { // a[1]..a[x]的和
int ans = 0;
while (x > 0) {
ans = ans + c[x];
x = x - lowbit(x);
}
return ans;
}
void add(int x, int k) {
while (x <= n) { // 不能越界
c[x] = c[x] + k;
x = x + lowbit(x);
}
}

典型应用:二维树状数组。

2. 状态压缩

当状态维数很多,但是只考虑取舍时,可以考虑状态压缩。

以后补坑。

3. 01-Trie

是一个简单轻巧的结构,结合了 Trie 的特点,适合运用在优化二进制题目中。

见字如面,字典树,就是储存字的典树。

Trie 利用树前缀存储方式存储字,而像 01-Trie 这样只有两个关键键值,Trie 的空间优势和深度优势就显示了出来。

比如说:

题目描述

农民约翰在喂奶牛的时候被另一个问题卡住了。他的所有 个奶牛在他面前排成一行(按序号 的顺序),按照它们的社会等级排序。奶牛 #1 由最高的社会等级,奶牛 #N 最低。

每个奶牛同时被赋予了一个唯一的数 的范围内。

帮助农民约翰找出应该从哪一头奶牛开始喂,使得从它开始的某一个连续的子序列上的奶牛的数的异或值最大。

简化题面

已知一个数列 长度为 ,求数列 子串异或和的最大值。

输入格式

第一行一个整数 ,表示奶牛的数量。

第二行 个整数,表示奶牛的数

输出格式

一行一个数字,所求得的异或和的最大值。


这道题目是经典的二进制贪心。

将异或前缀和,就变成了寻找两个值异或的最大值。

而选出两个值使异或最大,又由于二进制的贪心性(优先选择高位,即 ),可以考虑建立一颗 01-Trie 维护所有的值,对应每个值贪心寻找最大的数。

详细一步说,双指针,每次向下探索时都观察能不能和选出的数反着来(异或)。由于贪心,不需要考虑后续,正确性可以保证。

01-Trie 高度为 ,因此

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
// Copyright 2023 Lotuses
#include <cstdio>
#include <cstring>
#include <vector>

template<typename T>
void read(T &r) { r = 0; static char ch, last; ch = getchar(), last = 'z'; while (ch < '0' || ch > '9') last = ch, ch = getchar(); while (ch >= '0' && ch <= '9') r = (r << 1) + (r << 3) + (ch ^ 48), ch = getchar(); r = (last == '-') ? -r : r; }
template<typename T, typename...Ts>
void read(T &arg, Ts&...arg_left) { read(arg); read(arg_left...); }

template<typename T>
void write(T x) { if (x < 0) putchar('-'), x = -x; int len = 0; static char ch[100]; while (x) ch[++len] = x % 10 + '0', x /= 10; if (!len) ch[++len] = '0'; while (len) putchar(ch[len--]); }
template<typename T, typename...Ts>
void write(T arg, Ts...arg_left) { write(arg); putchar(' '); write(arg_left...); }
template<typename T>
void writeln(T x) { write(x); putchar('\n'); }
template<typename T, typename...Ts>
void writeln(T arg, Ts...arg_left) { write(arg); putchar(' '); write(arg_left...); putchar('\n'); }

// #define __DEBUG
#ifdef __DEBUG
#define debug(arg, args...) { printf("db <%d> ", __LINE__); writeln(arg, ##args); }
#else
#define debug(arg, args...) {}
#endif

int cs[1000006];

struct Trie {
struct Node {
int lc, rc;
int vis;
};

Node nd[1 << 25];
int cnt = 0;

int init(int h) {
if (h >= 23) {
return -1;
}
int id = ++cnt;
nd[id].vis = 0;
nd[id].lc = init(h + 1);
nd[id].rc = init(h + 1);
return id;
}

int num[30];

void invert(int x) {
int i = 21;
while (i >= 0) {
num[i] = x & 1;
i--; x >>= 1;
}
}

void insert() {
int pt = 1, i = 0;
while (i < 22) {
if (num[i] == 0) {
pt = nd[pt].lc;
} else {
pt = nd[pt].rc;
}
nd[pt].vis++; i++;
}
}

int query() {
int pt = 1, i = 0, p = 1, ans = 0;
while (i < 22) {
if (num[i] == 0) {
if (nd[nd[p].rc].vis) {
p = nd[p].rc;
ans++;
} else {
p = nd[p].lc;
}
pt = nd[pt].lc;
} else {
if (nd[nd[p].lc].vis) {
p = nd[p].lc;
ans++;
} else {
p = nd[p].rc;
}
pt = nd[pt].rc;
}
ans <<= 1; i++;
}
return ans >> 1;
}
} tr;

int main() {
#ifdef LOCAL
freopen("data/2.in", "r", stdin);
freopen("data/2.out", "w", stdout);
#endif

int n, c;
tr.init(0);
read(n);
for (int i = 1; i <= n; i++) {
read(c);
cs[i] = cs[i - 1] ^ c;
tr.invert(cs[i]);
tr.insert();
}

int ans = 0;
for (int i = 0; i < n; i++) {
tr.invert(cs[i]);
ans = std::max(tr.query(), ans);
}
writeln(ans);
return 0;
}

Trie 应用题

P2580 于是他错误的点名开始了

Trie 模板,但是需要注意空间问题。

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
// Copyright 2023 Lotuses
#include <cstdio>
#include <cstring>
#include <vector>
#include <string>
#include <iostream>

template<typename T>
void read(T &r) { r = 0; static char ch, last; ch = getchar(), last = 'z'; while (ch < '0' || ch > '9') last = ch, ch = getchar(); while (ch >= '0' && ch <= '9') r = (r << 1) + (r << 3) + (ch ^ 48), ch = getchar(); r = (last == '-') ? -r : r; }
template<typename T, typename...Ts>
void read(T &arg, Ts&...arg_left) { read(arg); read(arg_left...); }

template<typename T>
void write(T x) { if (x < 0) putchar('-'), x = -x; int len = 0; static char ch[100]; while (x) ch[++len] = x % 10 + '0', x /= 10; if (!len) ch[++len] = '0'; while (len) putchar(ch[len--]); }
template<typename T, typename...Ts>
void write(T arg, Ts...arg_left) { write(arg); putchar(' '); write(arg_left...); }
template<typename T>
void writeln(T x) { write(x); putchar('\n'); }
template<typename T, typename...Ts>
void writeln(T arg, Ts...arg_left) { write(arg); putchar(' '); write(arg_left...); putchar('\n'); }

// #define __DEBUG
#ifdef __DEBUG
#define debug(arg, args...) { printf("db <%d> ", __LINE__); writeln(arg, ##args); }
#else
#define debug(arg, args...) {}
#endif

struct Trie {
struct Node {
int cnt;
int sons[26];
} nd[1000001];
int cnt;
void init() {
cnt = 1;
// memset(nd, 0, sizeof(nd));
}
int insert(std::string s, int p = 1) {
int cur = 1;
for (int i = 0; i < s.length(); i++) {
if (nd[cur].sons[s[i] - 'a']) {
cur = nd[cur].sons[s[i] - 'a'];
} else {
if (p == 0) return 0; // MLE !!!
++cnt;
nd[cur].sons[s[i] - 'a'] = cnt;
cur = cnt;
}
}
nd[cur].cnt += p;
return nd[cur].cnt;
}
} stu, rel;

int main() {
#ifdef LOCAL
freopen(".in", "r", stdin);
freopen(".out", "w", stdout);
#endif

stu.init();
rel.init();
int n; std::string s;
read(n);
for (int i = 1; i <= n; i++) {
std::cin >> s;
stu.insert(s);
}
read(n);
for (int i = 1; i <= n; i++) {
std::cin >> s;
int re = stu.insert(s, 0);
if (!re) {
puts("WRONG");
} else {
if (rel.insert(s) == 1) {
puts("OK");
} else {
puts("REPEAT");
}
}
}
return 0;
}

P2922 [USACO08DEC] Secret Message G

难度不高,也算是 01-Trie 模板题,不过要注意题目描述,不然可能获得不到 分的好成绩。

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
// Copyright 2023 Lotuses
#include <cstdio>
#include <cstring>
#include <vector>

template<typename T>
void read(T &r) { r = 0; static char ch, last; ch = getchar(), last = 'z'; while (ch < '0' || ch > '9') last = ch, ch = getchar(); while (ch >= '0' && ch <= '9') r = (r << 1) + (r << 3) + (ch ^ 48), ch = getchar(); r = (last == '-') ? -r : r; }
template<typename T, typename...Ts>
void read(T &arg, Ts&...arg_left) { read(arg); read(arg_left...); }

template<typename T>
void write(T x) { if (x < 0) putchar('-'), x = -x; int len = 0; static char ch[100]; while (x) ch[++len] = x % 10 + '0', x /= 10; if (!len) ch[++len] = '0'; while (len) putchar(ch[len--]); }
template<typename T, typename...Ts>
void write(T arg, Ts...arg_left) { write(arg); putchar(' '); write(arg_left...); }
template<typename T>
void writeln(T x) { write(x); putchar('\n'); }
template<typename T, typename...Ts>
void writeln(T arg, Ts...arg_left) { write(arg); putchar(' '); write(arg_left...); putchar('\n'); }

// #define __DEBUG
#ifdef __DEBUG
#define debug(arg, args...) { printf("db <%d> ", __LINE__); writeln(arg, ##args); }
#else
#define debug(arg, args...) {}
#endif

struct Trie {
struct Node {
int cntv, cnte;
int s[2];
};
Node nd[500005];
int cnt;
void init() {
cnt = 1;
}
void insert(std::vector<int> &v) {
int cur = 1;
for (int i = 0; i < v.size(); i++) {
if (nd[cur].s[v[i]]) {
cur = nd[cur].s[v[i]];
} else {
++cnt;
nd[cur].s[v[i]] = cnt;
cur = cnt;
}
nd[cur].cntv++;
}
nd[cur].cnte++;
}
int query(std::vector<int> &v) {
int cur = 1, ans = 0;
for (int i = 0; i < v.size(); i++) {
ans += nd[cur].cnte;
if (nd[cur].s[v[i]]) {
cur = nd[cur].s[v[i]];
} else {
return ans;
}
}
return ans + nd[cur].cntv;
}
} tr;

int main() {
#ifdef LOCAL
freopen(".in", "r", stdin);
freopen(".out", "w", stdout);
#endif

tr.init();
std::vector<int> t;
int m, n, q, tt;
read(m, n);
for (int i = 1; i <= m; i++) {
t.clear();
read(q);
for (int j = 1; j <= q; j++) {
read(tt);
t.push_back(tt);
}
tr.insert(t);
}
for (int i = 1; i <= n; i++) {
t.clear();
read(q);
for (int j = 1; j <= q; j++) {
read(tt);
t.push_back(tt);
}
writeln(tr.query(t));
}
return 0;
}

P4551 最长异或路径

一道思维题,加入了第一道模板题的思想。

主要是运用了异或的自消性,只需要随便找个根,转换成前缀和,其他所有都搞定了。

不过虽然数据不会爆 int,但是有时候左移多了正好就爆了。

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
// Copyright 2023 Lotuses
#include <cstdio>
#include <cstring>
#include <vector>

template<typename T>
void read(T &r) { r = 0; static char ch, last; ch = getchar(), last = 'z'; while (ch < '0' || ch > '9') last = ch, ch = getchar(); while (ch >= '0' && ch <= '9') r = (r << 1) + (r << 3) + (ch ^ 48), ch = getchar(); r = (last == '-') ? -r : r; }
template<typename T, typename...Ts>
void read(T &arg, Ts&...arg_left) { read(arg); read(arg_left...); }

template<typename T>
void write(T x) { if (x < 0) putchar('-'), x = -x; int len = 0; static char ch[100]; while (x) ch[++len] = x % 10 + '0', x /= 10; if (!len) ch[++len] = '0'; while (len) putchar(ch[len--]); }
template<typename T, typename...Ts>
void write(T arg, Ts...arg_left) { write(arg); putchar(' '); write(arg_left...); }
template<typename T>
void writeln(T x) { write(x); putchar('\n'); }
template<typename T, typename...Ts>
void writeln(T arg, Ts...arg_left) { write(arg); putchar(' '); write(arg_left...); putchar('\n'); }

#define int long long

// #define __DEBUG
#ifdef __DEBUG
#define debug(arg, args...) { printf("db <%d> ", __LINE__); writeln(arg, ##args); }
#else
#define debug(arg, args...) {}
#endif

struct Edge {
int u, v, w;
};
std::vector<Edge> G[200005];

void insert(int u, int v, int w) {
G[u].push_back({u, v, w});
G[v].push_back({v, u, w});
}

struct Trie {
struct Node {
bool v;
int s[2];
};
Node nd[5000006];
int cnt;

void init() {
cnt = 1;
}

int cv[40];
void convert(int k) {
int i = 31;
while (i >= 1) {
cv[i] = k & 1;
k >>= 1;
i--;
}
}
void insert(int k) {
convert(k);
int cur = 1;
for (int i = 1; i <= 31; i++) {
if (nd[cur].s[cv[i]]) {
cur = nd[cur].s[cv[i]];
} else {
nd[cur].s[cv[i]] = ++cnt;
cur = cnt;
}
}
nd[cur].v = true;
}
int query(int k) {
convert(k);
int cur = 1, cp = 1, ans = 0;
for (int i = 1; i <= 31; i++) {
if (nd[cp].s[cv[i] ^ 1]) {
cp = nd[cp].s[cv[i] ^ 1];
ans++;
} else {
cp = nd[cp].s[cv[i]];
}
cur = nd[cur].s[cv[i]];
ans <<= 1; // 这里爆了 int 所以开了 ll 不过改不改无所谓反正过了
}
return ans >> 1;
}
} tr;

int xoor[200005];

void dfs(int u, int xr, int fa) {
tr.insert(xr);
xoor[u] = xr;
for (auto e : G[u]) {
if (e.v != fa) {
dfs(e.v, xr ^ e.w, u);
}
}
}

signed main() {
#ifdef LOCAL
freopen(".in", "r", stdin);
freopen(".out", "w", stdout);
#endif

int n;
read(n);
for (int i = 1; i < n; i++) {
static int u, v, w;
read(u, v, w);
insert(u, v, w);
}
tr.init();
dfs(1, 0, -1);
int ans = 0;
for (int i = 1; i <= n; i++) {
ans = std::max(ans, tr.query(xoor[i]));
}
writeln(ans);
return 0;
}

二进制题目不多,就先扯到这里。