并查集 总概
合并两个集合
查询某个节点的祖宗的节点
记录每个集合的大小:(绑定到根节点)
每个节点到根节点的距离:(绑定到每个节点上)
1250. 格子游戏 题目链接: 1250. 格子游戏
题解思路: 根据题目,将各个元素映射到一维数组中,连接起来,检查连接的过程中是否出现环(若出现环状-》出现连接的时候两个节点在同一个连通块中)就说明有圈了
题目代码: 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 #include <bits/stdc++.h> using namespace std;const int N = 41000 ;int p[N];int n, m;int get (int x, int y) { return x * n + y; } int find (int x) { if (p[x] != x) p[x] = find (p[x]); return p[x]; } int main () { cin >> n >> m; int res = 0 ; for (int i = 0 ; i < n * n; i++) p[i] = i; for (int i = 1 ; i <= m; i++){ int x, y; char d; cin >> x >> y >> d; int a, b; x--, y--; a = get (x, y); if (d == 'D' ) b = get (x + 1 , y); else b = get (x, y + 1 ); int pa = find (a), pb = find (b); if (pa == pb){ res = i; break ; } p[pa] = pb; } if (!res)cout << "draw" << endl; else cout << res << endl; return 0 ; }
1252. 搭配购买 题目链接: 1252. 搭配购买 - AcWing题库
题解思路: 思路:根据题目,将各个搭配连接起来,就相当于各个连通块选择,使得总价为vol,价值最大的01背包问题
题目代码: 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 #include <bits/stdc++.h> using namespace std;const int N = 10010 ;int v[N], w[N], p[N], f[N]; int n, m, vol;int find (int x) { if (p[x] != x) p[x] = find (p[x]); return p[x]; } int main () { cin >> n >> m >> vol; for (int i = 1 ; i <= n; i++) cin >> v[i] >> w[i]; for (int i = 1 ; i <= n; i++) p[i] = i; while (m --){ int a, b; cin >> a >> b; int pa = find (a), pb = find (b); if (pa != pb){ v[pb] += v[pa]; w[pb] += w[pa]; p[pa] = p[pb]; } } for (int i = 1 ; i <= n; i++){ if (p[i] == i) for (int j = vol; j >= v[i]; j--){ f[j] = max (f[j], f[j - v[i]] + w[i]); } } cout << f[vol] << endl; return 0 ; }
237. 程序自动分析 题目链接: 237. 程序自动分析
题解思路: 离散化+并查集
因为i,j的范围是1~1e9,所以需要离散化,然后就是基本的,相等的点连接到一个连通图里面,不相等的点进行判断。
离散化:
保序:排序+判重+二分
不保序: map、hash表
此题不保序
题目代码: 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 #include <bits/stdc++.h> using namespace std;const int N = 200010 ;int n, m, p[N];unordered_map<int , int > mp; struct Q { int x, y, e; }q[N]; int find (int x) { if (p[x] != x)p[x] = find (p[x]); return p[x]; } int get (int x) { if (mp.count (x) == 0 ) mp[x] = ++n; return mp[x]; } int main () { int T; cin >> T; while (T--){ cin >> m; n = 0 ; mp.clear (); for (int i = 1 ; i <= m; i++ ){ int x, y, e; cin >> x >> y >> e; q[i] = {get (x), get (y), e}; } for (int i = 1 ; i <= n; i++) p[i] = i; for (int i = 1 ; i <= m; i++){ if (q[i].e == 1 ){ int pa = find (q[i].x), pb = find (q[i].y); p[pa] = pb; } } int flag = 0 ; for (int i = 1 ; i <= m; i++){ if (q[i].e == 0 ){ int pa = find (q[i].x), pb = find (q[i].y); if (pa == pb) { flag = 1 ; break ; } } } if (flag)cout << "NO" << endl; else cout << "YES" << endl; } return 0 ; }
238. 银河英雄传说 题目链接: 238. 银河英雄传说
题解思路: 并查集:统一维护每个战舰到排头战舰的距离
当我们询问两个战舰间隔的时候是,|dx - dy| - 1 or 0(特判)
让排头当根节点->同时维护一个size表示当前队列的长度
假设有一个初始的队列a,新加一个队列b,就把队列b的排头d[b]+size[a],并且在find函数里面刷新后面所有d[x];
d[x]表示的是x到p[x]的距离,这样就可以很方便的算出每个节点到最排头的距离
题目代码: 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 #include <bits/stdc++.h> using namespace std;const int N = 30010 ;int p[N], d[N], Size[N], T;int find (int x) { if (p[x] != x){ int root = find (p[x]); d[x] += d[p[x]]; p[x] = root; } return p[x]; } int main () { cin >> T; for (int i = 1 ; i < N; i++) p[i] = i, Size[i] = 1 ; while (T--){ char op; int a, b; cin >> op >> a >> b; if (op == 'M' ){ int pa = find (a), pb = find (b); d[pa] = Size[pb]; Size[pb] += Size[pa]; p[pa] = pb; }else { int pa = find (a), pb = find (b); if (pa != pb){ cout << "-1" << endl; }else { if (a != b) cout << abs (d[a] - d[b]) - 1 << endl; else cout << 0 << endl; } } } }
239. 奇偶游戏 题目链接: 239. 奇偶游戏
题解思路: 总思想:前缀和
前缀和思想,如果是[l, r]
内有奇数个1的话那么必有S[r] - S[l-1]为奇数,也即是S[r]、S[l - 1]
奇偶性不同
同理如果是[l, r]
内有偶数个1的话那么必有S[r] - S[l-1]为偶数,也即是S[r]、S[l - 1]
奇偶性相同
方法一(扩展域) 枚举思想:
假设这个节点a是偶,并添加一个节点是a+M是奇
奇偶性相同的放在同一个集合中去
如果一组数据中,类型是偶数,就判断 a与b+M在不在同一个集合中
在同一个集合中说明矛盾
不在同一个集合中说明不矛盾
如果一组数据中,类型是奇数,就判断a与b是否在同一个集合中
在同一个集合中说明,矛盾
不在同一个集合中说明不矛盾
方法二(带边权) 维护一个带边权的并查集
d[x]表示x与p[x]的关系
x与y是同类
p[x] = p[y]
d[x] ^ d[y]是0无矛盾,d[x]^d[y]是1有矛盾
p[x] != p[y]
xy异类
p[x] = p[y]
d[x] ^ d[y]是0有矛盾,d[x]^d[y]是1无矛盾
p[x] != p[y]
d[p[x]] = d[x] ^ d[y] ^ 1
综上所述,就相当于代边权一个路径d表示x与根节点的关系,在find函数中需要对d进行更新类似于238题
题目代码: 方法一 (扩展域) 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 #include <bits/stdc++.h> using namespace std;const int N = 41000 , M = N / 2 ;int p[N], n, m;unordered_map<int , int > mp; int find (int x) { if (p[x] != x)p[x] = find (p[x]); return p[x]; } int get (int x) { if (mp.count (x) == 0 ) mp[x] = ++n; return mp[x]; } int main () { cin >> n >> m; n = 0 ; for (int i = 0 ; i < N; i++) p[i] = i; int res = m; for (int i = 1 ; i <= m; i++){ string t; int a, b; cin >> a >> b >> t; a = get (a - 1 ), b = get (b); if (t == "even" ){ if (find (a) == find (b + M)){ res = i - 1 ; break ; } p[find (a)] = find (b); p[find (a + M)] = find (b + M); }else { if (find (a) == find (b)){ res = i - 1 ; break ; } p[find (a)] = find (b + M); p[find (a + M)] = find (b); } } cout << res << endl; return 0 ; }
方法二(带边权) 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 #include <bits/stdc++.h> using namespace std;const int N = 41000 , M = N / 2 ;int p[N], d[N], n, m;unordered_map<int , int > mp; int find (int x) { if (p[x] != x){ int root = find (p[x]); d[x] += d[p[x]] % 2 ; p[x] = root; } return p[x]; } int get (int x) { if (mp.count (x) == 0 ) mp[x] = ++n; return mp[x]; } int main () { cin >> n >> m; n = 0 ; for (int i = 0 ; i < N; i++) p[i] = i; int res = m; for (int i = 1 ; i <= m; i++){ string t; int a, b; cin >> a >> b >> t; a = get (a - 1 ), b = get (b); if (t == "even" ){ int pa = find (a), pb = find (b); if (pa == pb){ if ((d[a] ^ d[b]) % 2 ){ res = i - 1 ; break ; } } else { p[pa] = pb; d[pa] = d[a] ^ d[b]; } }else { int pa = find (a), pb = find (b); if (pa == pb){ if ((d[a] ^ d[b]) % 2 == 0 ){ res = i - 1 ; break ; } } else { p[pa] = pb; d[pa] = d[a] ^ d[b] ^ 1 ; } } } cout << res << endl; return 0 ; }
树状数组 原理 引入问题 给出一个长度为n的数组,完成以下两种操作:
将第i个数加上k
输出区间[i,j]
内每个数的和
朴素算法 单点修改:O(1) 区间查询:O(n)
树状数组 快速求前缀和
单点修改:O(logn) 区间查询:O(logn)
lowbit() lowbit()运算:x & (-x) 的用途一般是用来获取某个二进制数的 LowBit ,在树状数组中会用到 lowbit(x)是x的二进制表达式中最低位的1所对应的值.
1 2 3 4 int lowbit (int x) { return x & -x; }
树状数组思想 树状数组的本质思想是使用树结构维护”前缀和”,从而把时间复杂度降为O(logn)。
对于一个序列,对其建立如下树形结构:
每个结点t[x]保存以x为根的子树中叶结点值的和
每个结点覆盖的长度为lowbit(x)
t[x]结点的父结点为t[x + lowbit(x)]
树的深度为log2n+1log2n+1
树状数组操作 add(x, k)表示将序列中第x个数加上k。
以add(3, 5)为例: 在整棵树上维护这个值,需要一层一层向上找到父结点,并将这些结点上的t[x]值都加上k,这样保证计算区间和时的结果正确。时间复杂度为O(logn)。
1 2 3 4 5 void add (int x, int k) { for (int i = x; i <= n; i += lowbit (i)) t[i] += k; }
ask(x)表示将查询序列前x个数的和
以ask(7)为例: 查询这个点的前缀和,需要从这个点向左上找到上一个结点,将加上其结点的值。向左上找到上一个结点,只需要将下标 x -= lowbit(x),
例如 7 - lowbit(7) = 6。
1 2 3 4 5 6 7 int ask (int x) { int sum = 0 ; for (int i = x; i; i -= lowbit (i)) sum += t[i]; return sum; }
树状数组初始化 若给你一个数组,时间卡的特别紧的话,需要O(n)的时间复杂度去初始化树状数组,那么可以On求出前缀和数组S
tr[x] = S[x] - S[x - lowbit(x)]
上述介绍引用:qiaoxinwei
241. 楼兰图腾 题目链接: 241. 楼兰图腾
题解思路: 树状数组
tr[x]保存数据在[1, x]的个数,t[r] - t[l-1]
代表在[l, r]
之间数据的个数
Greater[i]表示前i个数里面有多少个比a[i]大的数、Lower[i]表示前i个数里面有多少个比a[i]小的数
以i为中心,左边大于i的*右边大于i的就是以当前i为中心的所有V个数
题目代码: 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 #include <bits/stdc++.h> using namespace std;typedef long long ll;const int N = 210000 ;int a[N], tr[N], Greater[N], Lower[N], n;int lowbit (int x) { return x & -x; } void add (int x, int c) { for (int i = x; i <= n; i += lowbit (i)) tr[i] += c; } int sum (int x) { int res = 0 ; for (int i = x; i; i -= lowbit (i)) res += tr[i]; return res; } int main () { scanf ("%d" , &n); for (int i = 1 ; i <= n; i++){ scanf ("%d" , &a[i]); } for (int i = 1 ; i <= n; i++){ int y = a[i]; Greater[i] = sum (n) - sum (y); Lower[i] = sum (y - 1 ); add (y, 1 ); } memset (tr, 0 , sizeof tr); ll res1 = 0 , res2 = 0 ; for (int i = n; i; i -- ) { int y = a[i]; res1 += Greater[i] * (ll)(sum (n) - sum (y)); res2 += Lower[i] * (ll)(sum (y - 1 )); add (y, 1 ); } printf ("%lld %lld\n" , res1, res2); return 0 ; }
242. 一个简单的整数问题 题目链接: 242. 一个简单的整数问题
题解思路: 树状数组+差分
根据题目意思在a数组的[l, r]
区间每个元素加d,可以求a数组的差分,然后对a数组的差分来求树状数组
可以根据a数组的差分来求的树状数组,来还原a数组
题目代码: 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 #include <bits/stdc++.h> typedef long long ll;using namespace std;const int N = 100010 ;int a[N], n, m;ll tr[N]; int lowbit (int x) { return x & -x; } void add (int x, int c) { for (int i = x; i <= n; i += lowbit (i)) tr[i] += c; } ll sum (int x) { ll s = 0 ; for (int i = x; i; i -= lowbit (i)) s += tr[i]; return s; } int main () { cin >> n >> m; for (int i = 1 ; i <= n; i++) scanf ("%d" , &a[i]); for (int i = 1 ; i <= n; i++) add (i, a[i] - a[i - 1 ]); for (int i = 0 ; i < m; i++){ char op[2 ]; int l, r, d; scanf ("%s" , op); if (op[0 ] == 'Q' ){ scanf ("%d" , &l); printf ("%lld\n" , sum (l)); }else { scanf ("%d%d%d" , &l, &r, &d); add (l, d), add (r + 1 , -d); } } return 0 ; }
243. 一个简单的整数问题2 题目链接: 243. 一个简单的整数问题2
题解思路: 树状数组+差分
根据差分的思想,求出a的差分数组b
题目代码: 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 #include <bits/stdc++.h> typedef long long ll;using namespace std;const int N = 100010 ;ll tr1[N], tr2[N]; int n, m, a[N]; int lowbit (int x) { return x & -x; } void add (ll tr[], int x, ll c) { for (int i = x; i <= n; i += lowbit (i)) tr[i] += c; } ll sum (ll tr[], int x) { ll s = 0 ; for (int i = x; i; i -= lowbit (i)) s += tr[i]; return s; } ll pre_sum (int x) { return sum (tr1, x) * (x + 1 ) - sum (tr2, x); } int main () { cin >> n >> m; for (int i = 1 ; i <= n; i++) scanf ("%d" , &a[i]); for (int i = 1 ; i <= n; i++) { int b = a[i] - a[i - 1 ]; add (tr1, i, b); add (tr2, i, (ll)b * i); } while (m --){ char op[2 ]; int l, r, d; scanf ("%s%d%d" , op, &l, &r); if (op[0 ] == 'Q' ){ printf ("%lld\n" , pre_sum (r) - pre_sum (l - 1 )); }else { scanf ("%d" , &d); add (tr1, l, d), add (tr1, r + 1 , -d); add (tr2, l, d * l), add (tr2, r + 1 , -d * (r + 1 )); } } return 0 ; }
244. 谜一样的牛 题目链接: 244. 谜一样的牛
题解思路: 树状数组+二分查找
根据观察发现最后一头牛可以确定:最后一头牛前面有k个比它矮的牛,那么它必定是第k+1矮的牛
所以从后往前遍历,每次确定最后一头牛,他当前高度必定是剩下的牛种第k+1矮的(可以通过二分查找来确定每头牛的高度)
每确定一头牛,就删除这头牛的高度
题目代码: 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 #include <bits/stdc++.h> using namespace std;const int N = 100010 ;int h[N], res[N], tr[N], n;int lowbit (int x) { return x & -x; } void add (int x, int c) { for (int i = x; i <= n; i += lowbit (i)) tr[i] += c; } int sum (int x) { int s = 0 ; for (int i = x; i; i -= lowbit (i)) s += tr[i]; return s; } int main () { cin >> n; for (int i = 2 ; i <= n; i ++ ) scanf ("%d" , &h[i]); for (int i = 1 ; i <= n; i ++ ) tr[i] = lowbit (i); for (int i = n; i; i -- ) { int k = h[i] + 1 ; int l = 1 , r = n; while (l < r) { int mid = (l + r) >> 1 ; if (sum (mid) >= k) r = mid; else l = mid + 1 ; } res[i] = r; add (r, -1 ); } for (int i = 1 ; i <= n; i ++ ) printf ("%d\n" , res[i]); return 0 ; }
线段树 操作原理 操作
pushup(u):子节点的信息更新父节点的信息。
build():将一段区间初始化成线段树。
modify():修改。
单点:easy。
区间:pushdown(懒标记)hard。
类似于查询的时候,找到一个完整区间的时候直接标记一下(需要维护一个add)。
维护一个add(懒标记),以当前节点为根的子树中的每一个节点加上一个add(不包含根节点)。
在pushdown的时候被标记的节点如果继续往下递归的话,就将当前节点的标记取消并且递归到子节点上,这样所有叶子节点的祖宗节点的标记已经取消掉,并且累加到此叶子节点上了。
left.add += root.add, left.sum += (left.r - left.l + 1) * root.add
right.add += root.add, right += (right.r - right.l + 1) * root.add
root.add = 0
query():查询
查询某一个子区间的时候一定要pushdown一下
基本知识 满二叉树
线段树一般开空间:4n
1. 最大数 题目链接: 最大数
题解思路: 线段树
用线段树维护区间最大值
查询时
[tr.l , tr.r] 属于 [l, r]
直接返回[tr.l, tr.r]
区间内维护的最大值即可
l <= mid说明有左边部分,递归往左边查询即可
r>mid说明有右边部分,递归往右边查询即可
修改时
递归调用,直至叶节点,修改叶节点的值
pushup一下,将子节点的信息更新到父节点上
题目代码: 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 #include <bits/stdc++.h> using namespace std;typedef long long LL;const int N = 200010 ;int m, p;struct Node { int l, r; int v; }tr[4 *N]; void pushup (int u) { tr[u].v = max (tr[u << 1 ].v, tr[u << 1 | 1 ].v); } void build (int u, int l, int r) { tr[u] = {l, r}; if (l == r) return ; int mid = l + r >> 1 ; build (u << 1 , l, mid), build (u << 1 | 1 , mid + 1 , r); } int query (int u, int l, int r) { if (tr[u].l >= l && tr[u].r <= r) return tr[u].v; int mid = tr[u].l + tr[u].r >> 1 ; int v = 0 ; if (l <= mid) v = query (u << 1 , l , r); if (r > mid) v = max (v, query (u << 1 | 1 , l ,r)); return v; } void modify (int u, int x, int v) { if (tr[u].l == tr[u].r) tr[u].v = v; else { int mid = tr[u].l + tr[u].r >> 1 ; if (x <= mid) modify (u << 1 , x, v); else modify (u << 1 | 1 , x, v); pushup (u); } } int main () { int n = 0 , last = 0 ; cin >> m >> p; build (1 , 1 , m); while (m --){ char op[2 ]; cin >> op; if (op[0 ] == 'A' ){ int t;cin >> t; modify (1 , n + 1 , ((LL)t + last) % p); n++; }else { int L; cin >> L; last = query (1 , n - L + 1 , n); cout << last << endl; } } return 0 ; }
2. 你能回答这些问题吗 题目链接: 你能回答这些问题吗
题解思路: 思路:
维护四个数值:
tmax:最大连续子段和
lmax:最大前缀和
rmax:最大后缀和
sum:区间和
确定tmax:
$tmax = max(left-son.tmax, right-son.tmax, left-son.rmax + right-son.lmax)$
确定lmax,rmax:
以维护lmax为例:
完全在左区间内:左边的lmax
横跨两个区间:左边的sum+右边的lmax
rmax同理
题目代码: 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 #include <bits/stdc++.h> using namespace std;const int N = 500010 ;int w[N], n, m;struct Node { int l, r; int tmax, lmax, rmax, sum; }tr[4 * N]; void pushup (Node &u, Node &l, Node &r) { u.sum = l.sum + r.sum; u.lmax = max (l.lmax, l.sum + r.lmax); u.rmax = max (r.rmax, r.sum + l.rmax); u.tmax = max (max (l.tmax, r.tmax), l.rmax + r.lmax); } void pushup (int u) { pushup (tr[u], tr[u << 1 ], tr[u << 1 | 1 ]); } void build (int u, int l, int r) { if (l == r) tr[u] = {l, r, w[r], w[r], w[r], w[r]}; else { tr[u] = {l , r}; int mid = l + r >> 1 ; build (u << 1 , l, mid), build (u << 1 | 1 , mid + 1 , r); pushup (u); } } void modify (int u, int x, int v) { if (tr[u].l == x && tr[u].r == x) tr[u] = {x, x, v, v, v, v}; else { int mid = tr[u].l + tr[u].r >> 1 ; if (mid >= x)modify (u << 1 , x, v); else modify (u << 1 | 1 , x, v); pushup (u); } } Node query (int u, int l, int r) { if (tr[u].l >= l && tr[u].r <= r) return tr[u]; else { int mid = tr[u].l + tr[u].r >> 1 ; if (mid >= r) return query (u << 1 , l, r); else if (l > mid) return query (u << 1 | 1 , l, r); else { Node left = query (u << 1 , l, r); Node right = query (u << 1 | 1 , l, r); Node res; pushup (res, left, right); return res; } } } int main () { cin >> n >> m; for (int i = 1 ; i <= n; i++)scanf ("%d" , &w[i]); build (1 , 1 , n); while (m--){ int k, x, y; scanf ("%d%d%d" , &k, &x, &y); if (k == 1 ){ if (x > y) swap (x, y); printf ("%d\n" , query (1 , x, y).tmax); }else { modify (1 , x, y); } } }
3. 区间最大公约数 题目链接: 区间最大公约数
题解思路: 操作1:区间[l,r]
增加一个数
操作2:区间[l,r]
最大公约数
思路:
将区间增加一个数,转换为单点增加一个数,可以推出用差分。
证明差分数组和原数组某个区间内有共同的最大公因数。
($a1,a_2,···,a_n$)= ($a_1,a_2-a_1,···,a_n-a {n-1} $)
gcd(a, b) = gcd(a, b - a)
gcd(a, b) = gcd(a, -b)
查询某个区间[l, r]
的最大公约数
$gcd(a[l], gcd(b[l + 1], b[r]))$
题目代码: 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 #include <bits/stdc++.h> typedef long long LL;using namespace std;const int N = 500100 ;int n, m; LL w[N]; struct Node { int l, r; LL sum, d; }tr[4 * N]; LL gcd (LL a, LL b) { return b ? gcd (b, a % b) : a; } void pushup (Node &u, Node &l, Node &r) { u.sum = l.sum + r.sum; u.d = gcd (l.d, r.d); } void pushup (int u) { pushup (tr[u], tr[u << 1 ], tr[u << 1 | 1 ]); } void build (int u, int l, int r) { tr[u].l = l, tr[u].r = r; if ( l == r ) tr[u].d = w[l] - w[l - 1 ], tr[u].sum = w[l] - w[l - 1 ]; else { int mid = l + r >> 1 ; build (u << 1 , l, mid); build (u << 1 | 1 , mid + 1 , r); pushup (u); } } void modify (int u, int x, LL v) { if ( tr[u]. r == x && tr[u].l == x ) tr[u].d = tr[u].sum + v, tr[u].sum += v; else { int mid = tr[u].l + tr[u].r >> 1 ; if ( mid >= x ) modify (u << 1 , x, v); else modify (u << 1 | 1 , x, v); pushup (u); } } Node query (int u, int l, int r) { if ( tr[u].l >= l && tr[u].r <= r ) return tr[u]; else { int mid = tr[u].l + tr[u].r >> 1 ; if ( mid >= r ) return query (u << 1 , l, r); else if ( mid < l ) return query (u << 1 | 1 , l, r); else { Node left = query (u << 1 , l, r); Node right = query (u << 1 | 1 , l, r); Node res; pushup (res, left, right); return res; } } } int main () { cin >> n >> m; for (int i = 1 ; i <= n; i++)scanf ("%lld" , &w[i]); build (1 , 1 , n); char op[2 ]; int l, r; LL d; while (m--){ scanf ("%s%d%d" , op, &l, &r); if (op[0 ] == 'Q' ){ auto left = query (1 , 1 , l); Node right ({0 , 0 , 0 , 0 }) ; if (l + 1 <= r) right = query (1 , l + 1 , r); printf ("%lld\n" , abs (gcd (left.sum, right.d))); }else { scanf ("%lld" , &d); modify (1 , l, d); if ( r + 1 <= n ) modify (1 , r + 1 , -d); } } return 0 ; }
4. 一个简单的整数问题2 题目链接: 一个简单的整数问题2
题解思路: 最基本的线段树做法(添加懒标记)
类似于查询的时候,找到一个完整区间的时候直接标记一下(需要维护一个add)。
维护一个add(懒标记),以当前节点为根的子树中的每一个节点加上一个add(不包含根节点)。
在pushdown的时候被标记的节点如果继续往下递归的话,就将当前节点的标记取消并且递归到子节点上,这样所有叶子节点的祖宗节点的标记已经取消掉,并且累加到此叶子节点上了。
left.add += root.add, left.sum += (left.r - left.l + 1) * root.add
right.add += root.add, right += (right.r - right.l + 1) * root.add
root.add = 0
题目代码: 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 #include <bits/stdc++.h> typedef long long ll;using namespace std;const int N = 100010 ;int w[N], n, m;struct Node { int l, r; ll sum, add; }tr[N * 4 ]; void pushup (int u) { tr[u].sum = tr[u << 1 ].sum + tr[u << 1 | 1 ].sum; } void build (int u, int l, int r) { if (l == r) tr[u] = {l, r, w[r], 0 }; else { tr[u] = {l, r}; int mid = l + r >> 1 ; build (u << 1 , l, mid), build (u << 1 | 1 , mid + 1 , r); pushup (u); } } void pushdown (int u) { auto &root = tr[u], &left = tr[u << 1 ], &right = tr[u << 1 | 1 ]; if (root.add){ left.add += root.add, left.sum += (ll)(left.r - left.l + 1 ) * root.add; right.add += root.add, right.sum += (ll)(right.r - right.l + 1 ) * root.add; root.add = 0 ; } } void modify (int u, int l, int r, int d) { if (tr[u].l >= l && tr[u].r <= r){ tr[u].sum += (ll)(tr[u].r - tr[u].l + 1 ) * d; tr[u].add += d; }else { pushdown (u); int mid = tr[u].l + tr[u].r >> 1 ; if (l <= mid) modify (u << 1 , l, r, d); if (r > mid) modify (u << 1 | 1 , l, r, d); pushup (u); } } ll query (int u, int l, int r) { if (tr[u].l >= l && tr[u].r <= r) return tr[u].sum; pushdown (u); int mid = tr[u].l + tr[u].r >> 1 ; ll sum = 0 ; if (l <= mid) sum = query (u << 1 , l, r); if (r > mid) sum += query (u << 1 | 1 , l, r); return sum; } int main () { cin >> n >> m; for (int i = 1 ; i <= n; i++) scanf ("%d" , &w[i]); build (1 , 1 , n); char op[2 ]; int l, r, d; while (m --){ scanf ("%s%d%d" , op, &l, &r); if (op[0 ] == 'C' ){ scanf ("%d" , &d); modify (1 , l, r, d); } else printf ("%lld\n" , query (1 , l, r)); } return 0 ; }
5. 亚特兰蒂斯 题目链接: 亚特兰蒂斯
题解思路: 线段树+扫描线+离散化
操作一:将某一个区间[l, r] + k
操作二:整个区间中权值大于0的总长是多少
需要用离散化去处理y坐标,但是线段树维护的是y这个区间。
线段树中的节点信息:
cnt 当前区间整个被覆盖的次数
len 不考虑祖先节点cnt的前提下cnt>0的区间总长
扫描线:
永远只考虑根节点的信息(说明在query的时候不需要pushdown)
所有操作都是成对出现,且先加后减
题目代码: 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 #include <bits/stdc++.h> using namespace std;const int N = 100010 ;vector<double >ys; int n;struct segment { double x, y1, y2; int d; bool operator < (const segment&t)const { return x < t.x; } }seg[N * 2 ]; struct node { int l,r; int cnt; double len; }tr[N * 8 ]; int find (double y) { return lower_bound (ys.begin (), ys.end (), y) - ys.begin (); } void pushup (int u) { if (tr[u].cnt)tr[u].len = ys[tr[u].r + 1 ] - ys[tr[u].l]; else if (tr[u].l != tr[u].r){ tr[u].len = tr[u << 1 ].len + tr[u << 1 | 1 ].len; } else tr[u].len = 0 ; } void modify (int u,int l,int r,int d) { if (tr[u].l >= l && tr[u].r <= r) { tr[u].cnt += d; pushup (u); }else { int mid = tr[u].r + tr[u].l >> 1 ; if (l <= mid)modify (u << 1 , l, r, d); if (r > mid)modify (u << 1 | 1 , l, r, d); pushup (u); } } void build (int u,int l,int r) { tr[u] = {l, r, 0 , 0 }; if (l != r){ int mid = l + r >> 1 ; build (u << 1 ,l,mid),build (u << 1 | 1 ,mid + 1 ,r); } } int main () { int T = 1 ; while (cin >> n && n) { ys.clear (); int j = 0 ; for (int i = 0 ; i < n ; i ++) { double x1,y1,x2,y2; cin>>x1>>y1>>x2>>y2; seg[j ++] = {x1, y1, y2, 1 }; seg[j ++] = {x2, y1, y2, -1 }; ys.push_back (y1), ys.push_back (y2); } sort (seg,seg + j); sort (ys.begin (),ys.end ()); ys.erase (unique (ys.begin (),ys.end ()),ys.end ()); build (1 , 0 , ys.size () - 2 ); double res = 0 ; for (int i = 0 ; i < j ; i ++) { if (i)res += tr[1 ].len * (seg[i].x - seg[i - 1 ].x); modify (1 , find (seg[i].y1), find (seg[i].y2) - 1 , seg[i].d); } printf ("Test case #%d\n" , T ++ ); printf ("Total explored area: %.2lf\n\n" , res); } return 0 ; }
6. 维护序列 题目链接: 维护序列
题解思路: 线段树需要维护的信息:
sum、add、mul 需要有两个懒标记,先乘再加,方便更新mul和add
只是基础线段树的一个拓展
题目代码: 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 #include <bits/stdc++.h> using namespace std;typedef long long LL;const int N = 100010 ;int n, p, m;int w[N];struct Node { int l, r; int sum, add, mul; }tr[N * 4 ]; void pushup (int u) { tr[u].sum = (tr[u << 1 ].sum + tr[u << 1 | 1 ].sum) % p; } void eval (Node &t, int add, int mul) { t.sum = ((LL)t.sum * mul + (LL)(t.r - t.l + 1 ) * add) % p; t.mul = (LL)t.mul * mul % p; t.add = ((LL)t.add * mul + add) % p; } void pushdown (int u) { eval (tr[u << 1 ], tr[u].add, tr[u].mul); eval (tr[u << 1 | 1 ], tr[u].add, tr[u].mul); tr[u].add = 0 , tr[u].mul = 1 ; } void build (int u, int l, int r) { if (l == r) tr[u] = {l, r, w[r], 0 , 1 }; else { tr[u] = {l, r, 0 , 0 , 1 }; int mid = l + r >> 1 ; build (u << 1 , l, mid), build (u << 1 | 1 , mid + 1 , r); pushup (u); } } void modify (int u, int l, int r, int add, int mul) { if (tr[u].l >= l && tr[u].r <= r) eval (tr[u], add, mul); else { pushdown (u); int mid = tr[u].l + tr[u].r >> 1 ; if (l <= mid) modify (u << 1 , l, r, add, mul); if (r > mid) modify (u << 1 | 1 , l, r, add, mul); pushup (u); } } int query (int u, int l, int r) { if (tr[u].l >= l && tr[u].r <= r) return tr[u].sum; pushdown (u); int mid = tr[u].l + tr[u].r >> 1 ; int sum = 0 ; if (l <= mid) sum = query (u << 1 , l, r); if (r > mid) sum = (sum + query (u << 1 | 1 , l, r)) % p; return sum; } int main () { cin >> n >> p; for (int i = 1 ; i <= n; i ++ ) scanf ("%d" , &w[i]); build (1 , 1 , n); cin >> m; while (m --) { int t, l, r, d; scanf ("%d%d%d" , &t, &l, &r); if (t == 1 ) { scanf ("%d" , &d); modify (1 , l, r, 0 , d); } else if (t == 2 ) { scanf ("%d" , &d); modify (1 , l, r, d, 1 ); } else printf ("%d\n" , query (1 , l, r)); } return 0 ; }
可持久化数据结构
trie的可持久化
线段树的可持久化——主席树
可持久化的前提:本身拓扑结构、不变
解决的问题:将所有数据结构的历史版本都保存下来(有点像git)
核心思想:只记录每一个版本与前一个版本不同的节点
1. 最大异或和 题目链接: 最大异或和
题解思路:
求出前缀异或和,每次所求就是:$S{p-1}\ xor\ S {N}\ xor \ x = c$使得在l,r内c最大。
但是会有A操作在数组尾添加元素,这样就会影响到查询的结果,所有用可持久化维护各个版本,求[1, R]可用root[R]以及上述公式来求最大c。
但是同时又得求[L ,R]之内得最大c,所有需要trie维护一个信息,max-id(当前子树下标的最大值),只要当前子树下标最大值符合[L , R]就可以继续往当前子树查询。
题目代码: 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 #include <cstdio> #include <cstring> #include <algorithm> using namespace std;const int N = 600010 , M = N * 25 ;int n, m;int s[N]; int tr[M][2 ];int max_id[M]; int root[N], idx;void insert (int i, int k, int p, int q) { if (k < 0 ) { max_id[q] = i; return ; } int v = s[i] >> k & 1 ; if (p) tr[q][v ^ 1 ] = tr[p][v ^ 1 ]; tr[q][v] = ++idx; insert (i, k - 1 , tr[p][v], tr[q][v]); max_id[q] = max (max_id[tr[q][0 ]], max_id[tr[q][1 ]]); } int query (int r, int C, int l) { int p = root[r]; for (int i = 23 ; i >= 0 ; i--) { int v = C >> i & 1 ; if (max_id[tr[p][v ^ 1 ]] >= l) p = tr[p][v ^ 1 ]; else p = tr[p][v]; } return C ^ s[max_id[p]]; } int main () { scanf ("%d%d" , &n, &m); max_id[0 ] = -1 ; root[0 ] = ++idx; insert (0 , 23 , 0 , root[0 ]); for (int i = 1 ; i <= n; i++) { int x; scanf ("%d" , &x); s[i] = s[i - 1 ] ^ x; root[i] = ++idx; insert (i, 23 , root[i - 1 ], root[i]); } char op[2 ]; int l, r, x; while (m--) { scanf ("%s" , op); if (op[0 ] == 'A' ) { scanf ("%d" , &x); n++; s[n] = s[n - 1 ] ^ x; root[n] = ++idx; insert (n, 23 , root[n - 1 ], root[n]); } else { scanf ("%d%d%d" , &l, &r, &x); printf ("%d\n" , query (r - 1 , s[n] ^ x, l - 1 )); } } return 0 ; }
2. 可持久化线段树(主席树) 题目链接: 最大异或和
题解思路: 此题可用方法:
树套树:($O(mlog^2n)$)支持修改
划分树:($O(nlogn)$)不支持修改
主席树:($O(nlogn)$)
采用主席树:
核心:把新得点替换掉原来的点,结构一样,信息不一样
离散化
在数值上建立线段树,维护每个数值区间个数,求整体第k小的数。
缺点:难以进行区间修改操作
0————-mid————-n-1 mid的左边的个数cnt如果大于k说明第k个数在左边,cnt <= k 说明在右边
此题可以维护每个版本的各个数值区间的个数,因为每个版本结构都是一样的,但是只是信息不同,所以只要用第R个版本减去L-1个版本就可以得到[L, R]这一段这个区间里面0————-mid————-n-1有多少个数。
题目代码: 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 #include <bits/stdc++.h> using namespace std;const int N = 100010 ;int n, m;int a[N], root[N], idx;vector<int > nums; struct Node { int l, r; int cnt; }tr[N * 4 + N * 17 ]; int find (int x) { return lower_bound (nums.begin (), nums.end (), x) - nums.begin (); } int build (int l, int r) { int p = ++idx; if (l == r)return p; int mid = l + r >> 1 ; tr[p].l = build (l, mid), tr[p].r = build (mid + 1 , r); return p; } int insert (int p, int l, int r, int x) { int q = ++idx; tr[q] = tr[p]; if (l == r){ tr[q].cnt++; return q; } int mid = l + r >> 1 ; if (x <= mid) tr[q].l = insert (tr[p].l, l, mid, x); else tr[q].r = insert (tr[p].r, mid + 1 , r, x); tr[q].cnt = tr[tr[q].l].cnt + tr[tr[q].r].cnt; return q; } int query (int q, int p, int l, int r, int k) { if (l == r)return r; int cnt = tr[tr[q].l].cnt - tr[tr[p].l].cnt; int mid = l + r >> 1 ; if (k <= cnt) return query (tr[q].l, tr[p].l, l, mid, k); else return query (tr[q].r, tr[p].r, mid+1 , r, k - cnt); } int main () { cin >> n >> m; for (int i = 1 ; i <= n; i++) { cin >> a[i]; nums.push_back (a[i]); } sort (nums.begin (), nums.end ()); nums.erase (unique (nums.begin (), nums.end ()), nums.end ()); root[0 ] = build (0 , nums.size () - 1 ); for (int i = 1 ; i <= n; i++){ root[i] = insert (root[i - 1 ], 0 , nums.size () - 1 , find (a[i])); } while (m -- ){ int l, r, k; cin >> l >> r >> k; printf ("%d\n" , nums[query (root[r], root[l - 1 ], 0 , nums.size () - 1 , k)]); } return 0 ; }
平衡树——Treap treap、红黑树、splay、sbt、AVL
概念 对于一棵树,我们可以做一些特殊的操作,来让它变换树的形态结构,但是最后的答案却是正确的。平衡树的精髓就是这个,就是改变树的形态结构,但是不改变最后的中序遍历,也就是答案数组。
首先需要维护一个BST(二叉搜索树),一个根节点p,左儿子一定小于他,右儿子大于它。
为了维护这个BST我们需要一个左旋zag和右旋zig ,分别表示将根节点和左右儿子交换位置,使交换后还满足BST的性质 。
为了使得这个BST是一个尽可能随机的(添加随机数val维护这个堆 )使得这个BST的期望更加好一些(防止出现一条链的情况使得BST的时间复杂度达到O(n))
操作:
插入数值 xx。
删除数值 xx(若有多个相同的数,应只删除一个)。
查询数值 xx 的排名(若有多个相同的数,应输出最小的排名)。
查询排名为 xx 的数值。
求数值 xx 的前驱(前驱定义为小于 xx 的最大的数)。
求数值 xx 的后继(后继定义为大于 xx 的最小的数)。
1. 普通平衡树 题目链接: 普通平衡树
题解思路: Treap
维护键值key——BST,同时维护随机值val——堆
这样使得这颗树时间复杂度更加趋近于(logn),防止出现一条链
题目代码: 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 125 126 127 128 129 130 #include <bits/stdc++.h> using namespace std;const int N = 100010 , INF = 1e8 ;int n, root, idx;struct Node { int l, r; int key, val; int cnt, size; }tr[N]; void pushup (int u) { tr[u].size = tr[tr[u].l].size + tr[tr[u].r].size + tr[u].cnt; } int get_node (int key) { tr[ ++ idx].key = key; tr[idx].val = rand (); tr[idx].cnt = tr[idx].size = 1 ; return idx; } void zig (int &p) { int q = tr[p].l; tr[p].l = tr[q].r, tr[q].r = p, p = q; pushup (tr[p].r), pushup (p); } void zag (int &p) { int q = tr[p].r; tr[p].r = tr[q].l, tr[q].l = p, p = q; pushup (tr[p].l), pushup (p); } void build () { get_node (-INF), get_node (INF); root = 1 , tr[1 ].r = 2 ; pushup (root); if (tr[1 ].val < tr[2 ].val) zag (root); } void insert (int &p, int key) { if (!p) p = get_node (key); else if (tr[p].key == key) tr[p].cnt ++ ; else if (tr[p].key > key) { insert (tr[p].l, key); if (tr[tr[p].l].val > tr[p].val) zig (p); } else { insert (tr[p].r, key); if (tr[tr[p].r].val > tr[p].val) zag (p); } pushup (p); } void remove (int &p, int key) { if (!p) return ; if (tr[p].key == key) { if (tr[p].cnt > 1 ) tr[p].cnt -- ; else if (tr[p].l || tr[p].r) { if (!tr[p].r || tr[tr[p].l].val > tr[tr[p].r].val) { zig (p); remove (tr[p].r, key); } else { zag (p); remove (tr[p].l, key); } } else p = 0 ; } else if (tr[p].key > key) remove (tr[p].l, key); else remove (tr[p].r, key); pushup (p); } int get_rank_by_key (int p, int key) { if (!p) return 0 ; if (tr[p].key == key) return tr[tr[p].l].size + 1 ; if (tr[p].key > key) return get_rank_by_key (tr[p].l, key); return tr[tr[p].l].size + tr[p].cnt + get_rank_by_key (tr[p].r, key); } int get_key_by_rank (int p, int rank) { if (!p) return INF; if (tr[tr[p].l].size >= rank) return get_key_by_rank (tr[p].l, rank); if (tr[tr[p].l].size + tr[p].cnt >= rank) return tr[p].key; return get_key_by_rank (tr[p].r, rank - tr[tr[p].l].size - tr[p].cnt); } int get_prev (int p, int key) { if (!p) return -INF; if (tr[p].key >= key) return get_prev (tr[p].l, key); return max (tr[p].key, get_prev (tr[p].r, key)); } int get_next (int p, int key) { if (!p) return INF; if (tr[p].key <= key) return get_next (tr[p].r, key); return min (tr[p].key, get_next (tr[p].l, key)); } int main () { build (); cin >> n; while (n -- ) { int op, x; cin >> op >> x; if (op == 1 ) insert (root, x); else if (op == 2 ) remove (root, x); else if (op == 3 ) cout << get_rank_by_key (root, x) - 1 << endl; else if (op == 4 ) cout << get_key_by_rank (root, x + 1 ) << endl; else if (op == 5 ) cout << get_prev (root, x) << endl; else cout << get_next (root, x) << endl; } return 0 ; }
2. 营业额统计 题目链接: 营业额统计
题解思路: 可以用set的lower_bound(), upper_bound()
也可用平衡树
题目代码: 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 #include <bits/stdc++.h> typedef long long ll;using namespace std;const int N = 33000 , INF = 1e7 ;struct Node { int l, r; int key, val; }tr[N]; int root, idx, n;int get_node (int key) { tr[++idx].key = key; tr[idx].val = rand (); return idx; } void build () { get_node (-INF), get_node (INF); root = 1 , tr[1 ].r = 2 ; } void zig (int &p) { int q = tr[p].l; tr[p].l = tr[q].r, tr[q].r = p, p = q; } void zag (int &p) { int q = tr[p].r; tr[p].r = tr[q].l, tr[q].l = p, p = q; } void insert (int &p, int key) { if (!p) p = get_node (key); else if (tr[p].key == key) return ; else if (tr[p].key > key){ insert (tr[p].l, key); if (tr[tr[p].l].val > tr[p].val) zig (p); } else { insert (tr[p].r, key); if (tr[tr[p].r].val > tr[p].val) zag (p); } } int get_prev (int p, int key) { if (!p) return -INF; if (tr[p].key > key) return get_prev (tr[p].l, key); else return max (tr[p].key, get_prev (tr[p].r, key)); } int get_next (int p, int key) { if (!p) return INF; if (tr[p].key < key) return get_next (tr[p].r, key); return min (tr[p].key, get_next (tr[p].l, key)); } int main () { build (); cin >> n; ll res = 0 ; for (int i = 1 ; i <= n; i++){ int x; cin >> x; if (i == 1 )res += x; else { res += min (x - get_prev (root, x), get_next (root, x) - x); } insert (root, x); } cout << res << endl; return 0 ; }
AC自动机 ac自动机 = trie + kmp
next[i]: 在p中以p[i]结尾的后缀,能够匹配从1开始的(非平凡)前缀的最大长度
ac自动机就是对trie求next
用BFS的思想,一层一层遍历,对应于一维求next数组
1. 搜索关键词 题目链接: 搜索关键词
题解思路: 用BFS的思想,一层一层遍历,对应于一维求next数组
题目代码: 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 #include <bits/stdc++.h> using namespace std;const int N = 10010 , S = 55 , M = 1000010 ;int n;int tr[N * S][26 ], cnt[N * S], idx;char str[M];int q[N * S], ne[N * S], tt;void insert () { int p = 0 ; for (int i = 0 ; str[i]; i ++ ) { int t = str[i] - 'a' ; if (!tr[p][t]) tr[p][t] = ++ idx; p = tr[p][t]; } cnt[p] ++ ; } void build () { int hh = 0 , tt = -1 ; for (int i = 0 ; i < 26 ; i ++ ) if (tr[0 ][i]) q[ ++ tt] = tr[0 ][i]; while (hh <= tt) { int t = q[hh ++ ]; for (int i = 0 ; i < 26 ; i ++ ) { int c = tr[t][i]; if (!c)continue ; int j = ne[t]; while (j && !tr[j][i]) j = ne[j]; if (tr[j][i]) j = tr[j][i]; ne[c] = j; q[++tt] = c; } } } int main () { int T; cin >> T; while (T--){ memset (tr, 0 , sizeof tr); memset (cnt, 0 , sizeof cnt); memset (ne, 0 , sizeof ne); idx = 0 ; cin >> n; for (int i = 1 ; i <= n; i++){ scanf ("%s" , str); insert (); } build (); scanf ("%s" , str); int res = 0 ; for (int i = 0 , j = 0 ; str[i]; i++){ int t = str[i] - 'a' ; while (j && !tr[j][t]) j = ne[j]; if (tr[j][t]) j = tr[j][t]; int p = j; while (p){ res += cnt[p]; cnt[p] = 0 ; p = ne[p]; } } cout << res << endl; } return 0 ; }
可以优化成一个trie图
将空节点的指针指向它父节点的next指向的位置,这样就相当于每个空姐点都会直接指向它对应得next节点上去
有点路径压缩的感觉
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 #include <bits/stdc++.h> using namespace std;const int N = 10010 , S = 55 , M = 1000010 ;int n;int tr[N * S][26 ], cnt[N * S], idx;char str[M];int q[N * S], ne[N * S], tt;void insert () { int p = 0 ; for (int i = 0 ; str[i]; i ++ ) { int t = str[i] - 'a' ; if (!tr[p][t]) tr[p][t] = ++ idx; p = tr[p][t]; } cnt[p] ++ ; } void build () { int hh = 0 , tt = -1 ; for (int i = 0 ; i < 26 ; i ++ ) if (tr[0 ][i]) q[ ++ tt] = tr[0 ][i]; while (hh <= tt) { int t = q[hh ++ ]; for (int i = 0 ; i < 26 ; i ++ ) { int p = tr[t][i]; if (!p) tr[t][i] = tr[ne[t]][i]; else { ne[p] = tr[ne[t]][i]; q[++ tt] = p; } } } } int main () { int T; cin >> T; while (T--){ memset (tr, 0 , sizeof tr); memset (cnt, 0 , sizeof cnt); memset (ne, 0 , sizeof ne); idx = 0 ; cin >> n; for (int i = 1 ; i <= n; i++){ scanf ("%s" , str); insert (); } build (); scanf ("%s" , str); int res = 0 ; for (int i = 0 , j = 0 ; str[i]; i++){ int t = str[i] - 'a' ; j = tr[j][t]; int p = j; while (p){ res += cnt[p]; cnt[p] = 0 ; p = ne[p]; } } cout << res << endl; } return 0 ; }
2. 单词 题目链接: 单词
题解思路: 去求所有满足要求的前缀个数:
所有满足要求的前缀其后缀一定等于原串单词。
需要反向来求:
trie是通过next指针,根据地推的思想一直往前推,next可以求出前一个满足要求的前缀(可以和后缀匹配),可以一直跟着next指针一直往前走。
对于i来说可以用next指针将f[i] 累加到f[next[i]]上去,f的累加相当于是有向无环图,所有的和都会累加到终止节点上去,可以用拓扑排序的方法来求最终节点。
题目代码: 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 #include <bits/stdc++.h> using namespace std;const int N = 1000010 ;int n;int tr[N][26 ], f[N], q[N], ne[N], idx;char str[N];int id[210 ]; void insert (int x) { int p = 0 ; for (int i = 0 ; str[i]; i ++){ int t = str[i] - 'a' ; if (!tr[p][t]) tr[p][t] = ++idx; p = tr[p][t]; f[p] ++; } id[x] = p; } void build () { int hh = 0 , tt = -1 ; for (int i = 0 ; i < 26 ; i++){ if (tr[0 ][i])q[++tt] = tr[0 ][i]; } while (hh <= tt){ int t = q[hh++]; for (int i = 0 ; i < 26 ;i ++){ int &p = tr[t][i]; if (!p) p = tr[ne[t]][i]; else { ne[p] = tr[ne[t]][i]; q[++tt] = p; } } } } int main () { cin >> n; for (int i = 0 ; i < n; i++){ scanf ("%s" , str); insert (i); } build (); for (int i = idx - 1 ; i >= 0 ; i -- ) f[ne[q[i]]] += f[q[i]]; for (int i = 0 ; i < n; i ++ ) printf ("%d\n" , f[id[i]]); return 0 ; }