博客 > 数据结构&算法 > 备赛资料(含代码)
# 数据结构和字符串 ## 并查集 并查集常用来在一组数据之间记录等价划分,如分属不同连通分量的节点集、两类物品等等。 ```cpp int parent[N] = {0}; int find(int x) { int t = x, p; while(parent[t] != t) t = parent[t]; while(x != t) { p = parent[x]; parent[x] = t; x = p; } return t; } void merge(int a, int b) { parent[find(b)] = find(a); } ``` - 多域并查集 - 若给出的二元关系不是等价关系,最终任务是判断给出的某二元组是否和已知的某些关系矛盾,此时引入多域并查集。 - 例如有一批异性虫子两两配对交配(谁公谁母未知),判断有没有不是异性的二元组。 - 此时有两个关系(等价关系-同性别、非等价关系-异性别) - 若实体有$n$个,那么建立两倍大小的并查集空间$[0,n)\cup[n,2n)$,我们认为$(a,a)$为同性别,$(a,a+n)$互为异性别。对于存在的关系$(a,b)$,$(a,b+n)$和$(a+n,b)$是两个等价关系,将这两个二元组进行合并 - 例如,虫1与虫3交配,显然`~虫1性别=虫3性别`,`虫1性别=~虫3性别` - 当关系$(k,j)$前件和后件属于同一集合时,一定是发生了矛盾 - 矛盾并查集可以扩展到多种二元关系的情况下,如: - $A(x,y)$表示$x,y$是同类,$B(x,y)$表示$x$吃$y$,其中每条食物链必然构成形如 A-B-C-A 的环形 - 这时除了等价关系外,还存在两种二元关系:吃和被吃 - 此时可以构建三倍大小的并查集空间,$[0,n)\cup[n,2n)\cup[2n,3n)$,我们认为$x+n$代表$x$的天敌,$x+2n$代表$x$的食物;因此有$(a,b)$为同类,$(a,b+n)$为$a$等价于$b$的天敌,$(a, b+2n)$为$a$等价于$b$的食物 - 对$A(x,y)$,合并$(x,y)$,$(x+n,y+n)$,$(x+2n,y+2n)$;若$(x,y+n)$或$(x,y+2n)$已合并,则矛盾 - 对$B(x,y)$,合并$(x,y+n)$,$(x+2n,y)$,$(x+n,y+2n)$;若$(x,y)$或$(x+n,y)$已合并,则矛盾 - x带权并查集 - 多域并查集都可以用带权并查集替代 ## 树状数组 树状数组维护的是前缀和 ```cpp #define lowbit(x) ((x) & (-x)) int n; ll diff[N] = {0}; void add(int x, ll k) { while (x <= n) { diff[x] += k; x += lowbit(x); } } ll prefix(int x) { ll s = diff[x]; while(x -= lowbit(x)) s += diff[x]; return s; } ``` 如果从时间序列角度来考虑加减法事件的发生,可以形成一个差分前缀和,以此来解决区间加减法更新问题,即只在区间更新开始位置加差分值,在区间结束位置减差分值,只更新两个点而非区间上每个点。最终某位置的树状数组前缀和就是该位置的差分值 ## 线段树 几个关键: - `lazy`永远不影响本层值,本层值在`build`或`update`后就直接计算完毕。`pushdown`时才会用到`lazy`来处理下层值,并传递本层`lazy`给下层,留作下下层使用 - 但凡进入下层,必须`pushdown`;但凡更新下层的值,必须`pushup` - 分类讨论区间覆盖情况后,查询和更新的起止点在递归中无需变动: - 目标区间和节点区间无交集,直接跳出 - 目标区间完全覆盖节点区间,直接在本层处理 - 否则(不完全覆盖),下放给下层处理,也无需判断是否到左到右,因为即使有一面无交集也会由第一种情况跳出。但是要记得`pushdown/up` - 叶子节点一定符合前两种情况之一,因此`pushdown`永远不会影响叶子节点,也就是叶子节点的`lazy`永远不会起作用,是无所谓的变量 模板: ```cpp struct node { int l, r; ll v, lazy; }; node tree[N*4]; // 线段树最深层最多有2N个叶子节点,意味着整棵树有4N容量才能存下 int n, m; ll a[N]; void pushup(int x) { // 时刻保证到达的层的每个节点 v 值都正确 tree[x].v = tree[x*2].v + tree[x*2+1].v; } void pushdown(int x) { // 时刻保证到达的层的每个节点 v 值都正确 // lazy是给下层用的,而不给本层用 tree[x*2].v += tree[x].lazy*(tree[x*2].r - tree[x*2].l + 1); tree[x*2+1].v += tree[x].lazy*(tree[x*2+1].r - tree[x*2+1].l + 1); tree[x*2].lazy += tree[x].lazy; tree[x*2+1].lazy += tree[x].lazy; tree[x].lazy = 0; } // 构建线段树,处理l,r和v属性 void build(int l, int r, int x) { if (l == r) { tree[x] = node{l, r, a[l], 0}; return; } tree[x] = node{l, r, 0, 0}; int spli = (r-l)/2; // 二分 build(l, l+spli, x*2); build(l+spli+1, r, x*2+1); pushup(x); // x的v由子节点传递而来 } // 查询st-ed区间值,根据节点x的l-r区间分为: // st-ed和l-r无交集:无贡献 // st-ed包含l-r: 完全贡献,直接使用x的v // st-ed含l,r一端: 部分贡献,下放使用子节点结果 ll query(int st, int ed, int x) { // 不重合 if (tree[x].r < st || ed < tree[x].l) return 0; // 完全重合 if (st <= tree[x].l && tree[x].r <= ed) return tree[x].v; // 部分重合 ll res = 0; pushdown(x); // 下放懒标记 res += query(st, ed, x*2); res += query(st, ed, x*2+1); return res; } // 更新st-ed区间值,根据节点x的l-r区间分为: // st-ed和l-r无交集:不更新 // st-ed包含l-r: 批量更新x的v,然后除叶子外记录lazy // st-ed含l,r一端: 下放给子节点更新 void update(int st, int ed, int k, int x) { // 不重合 if (st > tree[x].r || ed < tree[x].l) return; // 完全重合 if (st <= tree[x].l && ed >= tree[x].r) { tree[x].v += k * (tree[x].r - tree[x].l + 1); tree[x].lazy += k; return; } // 部分重合 pushdown(x); // 下方之前的东西 update(st, ed, k, x*2); update(st, ed, k, x*2+1); pushup(x); // 向上传递v给x } ``` ## 二叉搜索树 - x Treap - x Splay ## 字符串 - 字典树Trie - 一图胜千言:![411f1b9a2b1cbf625f6c9a0e34661802.png](/resources/71d771ab58704c76a1c8336469248a15) - KMP: - ![f5aff97ff2878dc24de83d834e3fc43d.png](/resources/7661e067e82c4935a435ec9ec76dce90) - KMP可以用于求最长公共子串,不断裁剪或旋转模式串即可 - 先模式串自己匹配自己(逐长度判断前后缀共有长度)构造Next数组,然后模式串再匹配目标串(匹配双头前进,失配模式串回跳,模式串匹配完结束) - 代码直接背 ```cpp int next[N] = {0}; void getFail(string p) { for(int i = 1; i < p.length(); i++) { int j = next[i]; while(p[i] != p[j] && j) j = next[j]; if (p[i] == p[j]) next[i+1] = j+1; } } bool KMP(string s, string p) { int j = 0; getFail(p); for(int i = 0; i < s.length(); i++) { while(s[i] != p[j] && j) j = next[j]; if (s[i] == p[j]) j++; if (j == p.length()) return true; } return false; } ``` - x AC自动机 - x 后缀数组 - x 字符串哈希