DS 进阶
李超线段树:
Problem: 维护一个一次函数集合
,支持两个操作:
- 加入一条一次函数
- 给出
,求
首先对于一个一次函数,将
考虑改进这个朴素算法,我们考虑在每个节点上只保留一个函数。于是当两个函数同时挂到一个节点时,我们比较两个函数并得到他们的优势区间,显然只会有一个函数跨过区间,于是将另一个函数下放到它的优势区间。由于这是单边递归,时间复杂度显然是
注意到某些需要斜率优化的
例题。
code
#include<bits/stdc++.h> #define int long long using namespace std; const int N = 1e5 + 10; const double eps = 1e-9; struct Segment{ long double k, b; void init(int x0, int x1, int y0, int y1){ if(x0 == y0) k = 0, b = max(y0, y1); else k = 1.0 * (y1 - y0) / (x1 - x0), b = y0 - 1.0 * x0 * k; } double val(int x){return 1.0 * k * x + b;} }S[N]; // ask if x > y(x = y: 2) int cmp(double x, double y){ if(y - x > eps) return 0; if(x - y > eps) return 1; return 2; } // get maxid int max(int id1, int id2, int x){ int op = cmp(S[id1].val(x), S[id2].val(x)); if(op == 2) return (id1 < id2 ? id1 : id2); return (op ? id1 : id2); } struct Segtree{ #define ls (o << 1) #define rs (o << 1 | 1) #define mid (l + r >> 1) int tag[N << 2]; void upd(int o, int l, int r, int id1){ if(!tag[o]){tag[o] = id1; return;} if(max(id1, tag[o], mid) == id1) swap(id1, tag[o]); if(l == r) return; if(max(id1, tag[o], l) == id1) upd(ls, l, mid, id1); if(max(id1, tag[o], r) == id1) upd(rs, mid + 1, r, id1); } void findSeg(int o, int l, int r, int s, int t, int id){ if(s <= l && r <= t){upd(o, l, r, id); return;} if(s <= mid) findSeg(ls, l, mid, s, t, id); if(mid < t) findSeg(rs, mid + 1, r, s, t, id); } int qrymax(int o, int l, int r, int x){ if(l == r) return tag[o]; if(x <= mid) return max(tag[o], qrymax(ls, l, mid, x), x); else return max(tag[o], qrymax(rs, mid + 1, r, x), x); } }Seg; int n, tot, siz = 39990; void ADD(int& x, int y, int mod){x = (x + y - 1) % mod + 1;} signed main(){ ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); cin >> n; int lstans = 0; while(n--){ int opt; cin >> opt; if(opt == 0){ int pos; cin >> pos; ADD(pos, lstans, 39989); cout << (lstans = Seg.qrymax(1, 1, siz, pos)) << "\n"; } else{ int x0, y0, x1, y1; cin >> x0 >> y0 >> x1 >> y1; ADD(x0, lstans, 39989); ADD(x1, lstans, 39989); ADD(y0, lstans, 1e9); ADD(y1, lstans, 1e9); S[++tot].init(x0, x1, y0, y1); Seg.findSeg(1, 1, siz, min(x0, x1), max(x0, x1), tot); } } // system("pause"); return 0; }
线段树合并
Problem: 维护
个元素,初始时,每个元素自成一个集合。操作可以将两个集合合并,或对某个集合进行查询。
用动态开点线段树维护集合。合并两棵动态开点线段树时,只有双方重合的节点需要处理,耗时为“重合节点数目”。每次合并,总节点数都会减少“重合节点数目”,而总结点数是
这样讲可能过于抽象。来看例题 P3224 [HNOI2012] 永无乡。
连边等价于合并两个联通块,用并查集维护。对于一个联通块,我们用权值线段树维护即可。每次合并两个联通块时,只合并重复的节点即可。这样时间复杂度和空间复杂度均为
code
#include<bits/stdc++.h> using namespace std; const int N = 1e5 + 10; int n, m, fa[N], rev[N]; int findfa(int x){return fa[x] = (fa[x] == x) ? x : findfa(fa[x]);} struct node{ int ls, rs, sum; }t[N << 5]; int tot, rub[N << 5], tb; int newnode(int val){ int id = (tb ? rub[tb--] : (++tot)); t[id] = {0, 0, 1}; return id; } struct Segtree{ #define mid (l + r >> 1) int root; void pushup(int o){t[o].sum = t[t[o].ls].sum + t[t[o].rs].sum;} void init(int val){root = build(1, n, val);} int build(int l, int r, int val){ int o = newnode(val); if(l == r) return o; if(val <= mid) t[o].ls = build(l, mid, val); else t[o].rs = build(mid + 1, r, val); pushup(o); return o; } int getkth(int o, int l, int r, int k){ if(t[o].sum < k) return -1; if(l == r) return rev[l]; int lsiz = t[t[o].ls].sum; if(lsiz >= k) return getkth(t[o].ls, l, mid, k); else return getkth(t[o].rs, mid + 1, r, k - lsiz); } int merge(int o, int rt, int l, int r){ if((!o) || (!rt)) return o + rt; t[o].ls = merge(t[o].ls, t[rt].ls, l, mid); t[o].rs = merge(t[o].rs, t[rt].rs, mid + 1, r); pushup(o); rub[++tb] = rt; return o; } }Seg[N]; void merge(int x, int y){ int fx = findfa(x), fy = findfa(y); if(fx == fy) return; fa[fy] = fx; Seg[fx].merge(Seg[fx].root, Seg[fy].root, 1, n); } signed main(){ ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); cin >> n >> m; for(int i = 1; i <= n; i++){ int x; cin >> x; rev[x] = i; Seg[i].init(x); fa[i] = i; } for(int i = 1; i <= m; i++){ int x, y; cin >> x >> y; merge(x, y); } int T; cin >> T; while(T--){ char opt; int x, y; cin >> opt >> x >> y; if(opt == 'Q') cout << Seg[findfa(x)].getkth(Seg[findfa(x)].root, 1, n, y) << "\n"; else merge(x, y); } // system("pause"); return 0; }
线段树合并还可以用来优化树形
显然有一个
-
不割
和父亲的连边: 。 -
割
和父亲的连边:
于是用下标为时间的线段树维护
code
#include<bits/stdc++.h> #define int long long using namespace std; const int N = 1e5 + 10; struct edge{ int v, next; }edges[N << 1]; int head[N], idx; void add_edge(int u, int v){ edges[++idx] = {v, head[u]}; head[u] = idx; } int n, m, k, d[N], w[N]; struct node{ int ls, rs, mx, mn, tag; }t[N << 5]; int buc[N << 5], tot, tb; int crenode(){return (tb ? buc[tb--] : (++tot));} void delnode(int &id){ if(id == 0) return; t[id] = {0, 0, 0, 0, 0}; buc[++tb] = id; id = 0; } struct Segtree{ int root; #define mid (l + r >> 1) void pushup(int o){ t[o].mx = max(t[t[o].ls].mx, t[t[o].rs].mx) + t[o].tag; t[o].mn = min(t[t[o].ls].mn, t[t[o].rs].mn) + t[o].tag; } // merge two tree void merge(int &o, int &rt, int l, int r){ if((!o) || (!rt)){o = o + rt; return;} if(l == r){ t[o].tag += t[rt].tag; delnode(rt); t[o].mx = t[o].mn = t[o].tag; return; } t[o].tag += t[rt].tag; merge(t[o].ls, t[rt].ls, l, mid); merge(t[o].rs, t[rt].rs, mid + 1, r); pushup(o); delnode(rt); } // set [s, e] = v (e <= v) void modify(int &o, int l, int r, int s, int v){ if(!o) o = crenode(); //cout << o << " " << l << " " << r << " " << s << " " << v << "\n"; if(s <= l){ if(t[o].mx <= v){ delnode(t[o].ls); delnode(t[o].rs); t[o].tag = t[o].mx = t[o].mn = v; return; } else{ v -= t[o].tag; if(t[t[o].ls].mn < v) modify(t[o].ls, l, mid, s, v); if(t[t[o].rs].mn < v) modify(t[o].rs, mid + 1, r, s, v); pushup(o); return; } } v -= t[o].tag; if(s <= mid) modify(t[o].ls, l, mid, s, v); modify(t[o].rs, mid + 1, r, s, v); pushup(o); } int qrysingle(int o, int l, int r, int x){ if(!o) return 0; if(l == r) return t[o].tag; if(x <= mid) return t[o].tag + qrysingle(t[o].ls, l, mid, x); else return t[o].tag + qrysingle(t[o].rs, mid + 1, r, x); } int qryall(){return t[root].mx;} }Seg[N]; void dfs(int u, int fa){ for(int i = head[u]; i; i = edges[i].next){ int v = edges[i].v; if(v == fa) continue; dfs(v, u); Seg[u].merge(Seg[u].root, Seg[v].root, 1, k); } if(d[u]){ int s = w[u] + Seg[u].qrysingle(Seg[u].root, 1, k, d[u]); Seg[u].modify(Seg[u].root, 1, k, d[u], s); //cout << s << " " << Seg[u].qryall() << "\n"; } } signed main(){ ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); cin >> n >> m >> k; for(int i = 2; i <= n; i++){ int x; cin >> x; add_edge(x, i); add_edge(i, x); } for(int i = 1; i <= m; i++){ int v; cin >> v; cin >> d[v] >> w[v]; } dfs(1, 0); cout << Seg[1].qryall(); // system("pause"); return 0; }
线段树分治
线段树分治可以将 修改-查询-删除 这类操作以一个
先咕咕咕了。
猫树分治
对于一类可合并信息
猫树分治的具体思想就是对于一个区间
例题:
Problem: 给出长度为
的序列 和固定模数 , 个询问 ,每次查询 中有多少个子序列满足和是 的倍数。其中 。
不难直接用线段树维护背包,时间复杂度
接下来考虑猫树分治,显然对于一个背包加入一个点的时间复杂度是
code
#pragma GCC optimize(3, "Ofast", "inline") #include<bits/stdc++.h> #define int long long using namespace std; const int N = 2e5 + 10, mod = 1e9 + 7; int n, m, Q, a[N], ans[N]; struct node{ int val[20]; node(){val[0] = 1; for(int i = 1; i < 20; i++) val[i] = 0;} }bg[N]; void ADD(int& x, int y){x = (x + y) % mod;} void ADDbag(node& bg, int x){ node ret; for(int i = 0; i < 20; i++) ret.val[i] = bg.val[i]; for(int i = 0; i < 20; i++) ADD(ret.val[(i + x) % m], bg.val[i]); bg = ret; } struct query{ int l, r, id; }; namespace cattree{ #define ls (o << 1) #define rs (o << 1 | 1) #define mid (l + r >> 1) vector<query> vec[N << 2], Ql[N], Qr[N]; void clr(vector<query>& vvvv){vector<query> qwq; swap(qwq, vvvv);} void ADDqry(int o, int l, int r, int s, int t, int id){ if((s <= mid && mid < t) || l == r){vec[o].push_back((query){s, t, id}); return;} if(s <= mid) ADDqry(ls, l, mid, s, t, id); else ADDqry(rs, mid + 1, r, s, t, id); } void solve(int o, int l, int r){ if(l == r){ for(int i = 0; i < vec[o].size(); i++) ans[vec[o][i].id] = 1 + (a[l] == 0); return; } solve(ls, l, mid); solve(rs, mid + 1, r); for(int i = 0; i < vec[o].size(); i++) Ql[vec[o][i].l].push_back(vec[o][i]), Qr[vec[o][i].r].push_back(vec[o][i]); node lft, rht; for(int i = mid; i >= l; i--){ ADDbag(lft, a[i]); for(int j = 0; j < Ql[i].size(); j++) bg[Ql[i][j].id] = lft; } for(int i = mid + 1; i <= r; i++){ ADDbag(rht, a[i]); for(int j = 0; j < Qr[i].size(); j++){ int id = Qr[i][j].id; for(int k = 0; k < m; k++) ADD(ans[id], bg[id].val[k] * rht.val[(m - k) % m] % mod); } } for(int i = 0; i < vec[o].size(); i++) clr(Ql[vec[o][i].l]), clr(Qr[vec[o][i].r]); } } signed main(){ ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); cin >> n >> m; for(int i = 1; i <= n; i++) cin >> a[i], a[i] = a[i] % m; cin >> Q; for(int i = 1; i <= Q; i++){ int l, r; cin >> l >> r; cattree::ADDqry(1, 1, n, l, r, i); } cattree::solve(1, 1, n); for(int i = 1; i <= Q; i++) cout << ans[i] << "\n"; return 0; } /**/
Seg-beats
普通的线段树似乎好像不能快速维护这种东西。而我们伟大的吉如一老师给出了一个很优美的剪枝。(下面以区间取
-
:无事发生。 -
:令 即可。 -
:直接对这个节点的左右儿子进行暴力递归更新。
这样的时间复杂度是对的吗?事实上,对于
Segbeats 结合区间加操作时间复杂度是
矩阵原教旨主义
有的时候线段树上面有很多种类的标记和维护信息,人脑考虑不过来的时候,可以用一点常数来交换用脑量。
然后由于矩阵是半群信息,因此可以直接设计一个半群维护其中的有用信息即可。
左偏树
其实就是可合并堆但是非启发式合并。
考虑描述暴力合并的过程:
-
当两个节点重合时,选择更优的节点作为根节点,然后随便选一个子树和另外一棵树递归合并。
-
当只有一棵树不为空时,直接返回即可。
不难发现这个过程的复杂度实际上取决于第一种情况的出现次数。我们称满足第二种情况的节点为 “叶子”,然后定义一个节点的 “高度” 为节点与其子树内离其最近的叶子的距离,定义一棵树的高度为根节点的高度。
我们想要优化合并的复杂度,实际上就是要让树的高度尽量小。由于堆是二叉树,每次可以不管一个子树,把另一个子树合并即可。因此我们只要保证每个节点的某一个子树的高度尽量小即可,这里我们选择右子树。即让每个节点的
每次合并我们将根节点的右子树与另一个根节点合并即可。每次合并完若不满足左偏树的性质。直接交换左右子树即可。
令这样一棵有
模板题代码
#include<bits/stdc++.h> #define ll long long using namespace std; const int N = 1e5 + 10; struct node{ int ls, rs, val, id, dist, fa; }tn[N]; bool operator <(struct node n1, struct node n2){ if(n1.val != n2.val) return n1.val < n2.val; return n1.id < n2.id; } int n, m, vis[N]; int findfa(int x){return tn[x].fa = (tn[x].fa == x) ? x : findfa(tn[x].fa);} void pushup(int o){ if(tn[tn[o].rs].dist > tn[tn[o].ls].dist) swap(tn[o].ls, tn[o].rs); tn[o].dist = tn[tn[o].rs].dist + 1; } int merge(int x, int y){ if(!x || !y){tn[x + y].dist = 0; return x + y;} if(tn[y] < tn[x]) swap(x, y); tn[x].rs = merge(tn[x].rs, y); pushup(x); return x; } void Mer(int x, int y){ if(vis[x] || vis[y]) return; x = findfa(x); y = findfa(y); //cout << x << " " << y << "\n"; if(x == y) return; tn[x].fa = tn[y].fa = merge(x, y); } void del(int x){ if(vis[x]){cout << -1 << "\n"; return;} x = findfa(x); cout << tn[x].val << "\n"; //cout << x << " " << tn[x].ls << " " << tn[x].rs << "\n"; vis[x] = 1; tn[tn[x].ls].fa = tn[tn[x].rs].fa = tn[x].fa = merge(tn[x].ls, tn[x].rs); } signed main(){ ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); cin >> n >> m; tn[0].dist = -1; for(int i = 1; i <= n; i++) cin >> tn[i].val, tn[i].id = i, tn[i].fa = i; while(m--){ int op; cin >> op; if(op == 1){ int x, y; cin >> x >> y; Mer(x, y); } else{ int x, y; cin >> x; del(x); } } return 0; }
本文作者:Little_corn
本文链接:https://www.cnblogs.com/little-corn/p/18365936
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步