【学习笔记】线段树应用
标题用 ##,说明太水啦~
主要是以一些题目为例谈谈线段树的一些拓展用法,感觉线段树很神!
P2146 [NOI2015] 软件包管理器 树剖+线段树
树剖+线段树板子,这种树剖的题只是加了个树剖的壳把它转换为区间问题罢了。至于为什么,这里弱弱的引用神🐟的一张图:
关于各点的 dfn,可以发现两个性质:
- 一条重链上的点的 dfn 连续。
- 一棵子树上的点的 dfn 也连续。
本题中就主要有两个操作:
- 每次安装软件,就把根节点到 \(x\) 软件路径上的值全部变为 \(1\)。
- 每次卸载软件,就把 \(x\) 以及它的子树的值变为 \(0\)。
\(tr[root].sum\) 可维护 \(1\) 的个数,答案为操作前后 \(tr[root].sum\) 的差的绝对值。
Code
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e5+5;vector<int> g[N];
int sz[N], fa[N], son[N], dep[N];
int dfn[N], top[N], T;void dfs1(int u, int f){dep[u] = dep[f] + 1;sz[u] = 1;fa[u] = f;int maxson = -1;for(int v : g[u]){if(v == f) continue;dfs1(v, u);sz[u] += sz[v];if(maxson < sz[v]){maxson = sz[v];son[u] = v;}}
}void dfs2(int u, int topf){dfn[u] = ++T;top[u] = topf;if(!son[u]) return;dfs2(son[u], topf);for(int v : g[u]){if(dfn[v]) continue;dfs2(v, v);}
}struct node{int l, r;int sum, tag;#define ls x<<1#define rs x<<1|1
}tr[N<<2];void build(int x, int l, int r){tr[x].l = l, tr[x].r = r; tr[x].sum = 0; tr[x].tag = -1;if(l == r) return;int mid = (tr[x].l+tr[x].r)>>1;build(ls, l, mid);build(rs, mid+1, r);
}void pushup(int x){tr[x].sum = tr[ls].sum+tr[rs].sum;
}void pushdown(int x){if(tr[x].tag == -1) return;tr[ls].sum = tr[x].tag*(tr[ls].r-tr[ls].l+1);tr[rs].sum = tr[x].tag*(tr[rs].r-tr[rs].l+1);tr[ls].tag = tr[rs].tag = tr[x].tag;tr[x].tag = -1;
}void uby_interval(int x, int l, int r, int v){if(tr[x].l>=l && tr[x].r<=r){tr[x].sum = v*(tr[x].r-tr[x].l+1);tr[x].tag = v;return;}int mid = (tr[x].l+tr[x].r)>>1;pushdown(x);if(l<=mid) uby_interval(ls, l, r, v);if(r>mid) uby_interval(rs, l, r, v);pushup(x);
}void uby_lian(int x, int v){while(top[x] != 1){uby_interval(1, dfn[top[x]], dfn[x], v);x = fa[top[x]];}uby_interval(1, 1, dfn[x], v);
}int main(){ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);int n; cin>>n;for(int i=2; i<=n; i++){int x; cin>>x;g[x+1].push_back(i);}dfs1(1, 0);dfs2(1, 1);build(1, 1, n);int m; cin>>m;while(m--){string s; int x;cin>>s>>x; x++;int t1 = tr[1].sum;if(s=="install"){uby_lian(x, 1);cout<<tr[1].sum-t1<<"\n";} else{uby_interval(1, dfn[x], dfn[x]+sz[x]-1, 0);cout<<t1-tr[1].sum<<"\n";}}return 0;
}
P1486 [NOI2004] 郁闷的出纳员 权值线段树
关于权值线段树的介绍先推一篇博客。简单来说就是用线段树来维护桶。线段树的端点就是它所代表的值域。权值线段树的主要操作是求第 k 小/大。
考虑加工资不会对人数产生影响,我们直接维护偏移量即可。
对于扣工资操作,由于,可能会需要让一段工资区间内的人集体辞职,我们还需要在线段树上维护一个区间删除。
总的来说,我们对于工资建立一棵权值线段树,维护区间元素个数,需要支持单点插入,区间删除。同时维护一个偏移量,对于新插入的人进行偏移后插入至线段树中即可。
这里有一个 trick,为了防止下标变成负数,可以将权值为负的值加上一个最大的权值(也可以设为一个较大的值)。
Code
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 3e5+5;
const int rit = 2e5; // 防止减到负数struct node{int l, r;int sum;#define ls x<<1#define rs x<<1|1
}tr[(rit+rit)<<2];void pushup(int x){tr[x].sum = tr[ls].sum + tr[rs].sum;
}void build(int x, int l, int r){tr[x].l = l, tr[x].r = r, tr[x].sum = 0;if(tr[x].l == tr[x].r) return;int mid = (tr[x].l+tr[x].r)>>1;build(ls, l, mid); build(rs, mid+1, r);
}void update(int x, int val){if(tr[x].l == tr[x].r){tr[x].sum++;return;}int mid = (tr[x].l+tr[x].r)>>1;if(val<=mid) update(ls, val);if(val>mid) update(rs, val);pushup(x);
}int query(int x, int l, int r){if(tr[x].l >=l && tr[x].r <= r) return tr[x].sum;int mid = (tr[x].l+tr[x].r)>>1, res = 0;if(l <= mid) res += query(ls, l, r);if(r > mid) res += query(rs, l, r);return res;
}void clear(int x, int l, int r){if(tr[x].l == tr[x].r){tr[x].sum = 0;return;}int mid = (tr[x].l+tr[x].r)>>1;if(l <= mid && tr[ls].sum) clear(ls, l, r);if(r > mid && tr[rs].sum) clear(rs, l, r);pushup(x);
}int find_kth(int x, int k){if(tr[x].l == tr[x].r) return tr[x].l;if(tr[rs].sum >= k) return find_kth(rs, k);else return find_kth(ls, k-tr[rs].sum);
}int main(){ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);int n, minn, out = 0; cin>>n>>minn;int begin_minn = minn;minn += rit; // 修改工资的操作用修改min来代替int front = 0; // 已经修改的工资总量,每次加入员工时减去frontbuild(1, 1, rit+rit); // 以值域为端点建树for(int i=1; i<=n; i++){char s; int x; cin>>s>>x;if(s == 'I'){if(x < begin_minn) continue;update(1, x-front+rit);} else if(s == 'A'){front += x;minn -= x;} else if(s == 'S'){front -= x;minn += x;// 踢出员工int now = query(1, 0, minn-1);out += now;clear(1, 0, minn-1);} else{if(x > query(1, minn, rit+rit)) cout<<"-1\n";else cout<<find_kth(1, x)+front-rit<<"\n";}}cout<<out;return 0;
}
虽然这题用 pbds 更是平衡树裸题。
P2824 [HEOI2016/TJOI2016] 排序 线段树多次排序?
对于此题依旧推一篇博客。有亿些借鉴。
先考虑对一个 01 序列多次排序:
使用线段树来维护。查询一段区间内的 \(1\) 的个数记为 \(cnt1\),如果是升序,就将这段区间的 \([r-cnt1+1, r]\) 都更改为 \(1\),将 \([l, r-cnt1]\) 更改为 \(0\)。降序则将 \([l, l+cnt1-1]\) 更改为 \(1\),将 \([l+cnt, r]\) 更改为 \(0\)。这样我们就成功地把排序转化为了区间查询和区间修改。
然后就是最人类智慧的一集:
二分答案 \(mid\)。我们把原排列中大于等于 \(mid\) 的数都标记为 \(1\),小于 \(mid\) 的都标记为 \(0\)。然后对于每个操作我们就将 01 序列排个序。最后如果第 \(p\) 个位子仍是 \(1\) 的话就是可行的。
单调性:
假设一下,如果二分的答案是 \(1\),那么原序列所有的值都转化为了 \(1\),所以最后肯定是 true。如果二分一个值成立当且仅当这个位子的值大于等于 \(mid\),故如果 check 返回 true,则 \(l = mid+1\),否则 \(r = mid-1\)。
Code
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 2e5+5;int a[N];
int n, m, q;
struct optt{int op, l, r;
}opt[N];struct node{int l, r;// 区间查询和区间修改int sum, tag;#define ls x<<1#define rs x<<1|1
}tr[N<<2];void pushup(int x){tr[x].sum = tr[ls].sum+tr[rs].sum;
}void pushdown(int x){if(tr[x].tag == -1) return;tr[ls].sum = (tr[ls].r-tr[ls].l+1)*tr[x].tag;tr[rs].sum = (tr[rs].r-tr[rs].l+1)*tr[x].tag;tr[ls].tag = tr[rs].tag = tr[x].tag;tr[x].tag = -1;
}void build(int x, int l, int r, int p){tr[x].l = l, tr[x].r = r; tr[x].tag = -1;if(l == r){tr[x].sum = (a[l]>=p);return;}int mid = (l+r)>>1;build(ls, l, mid, p);build(rs, mid+1, r, p);pushup(x);
}int querynum(int x, int l, int r){if(tr[x].l>=l && tr[x].r<=r){return tr[x].sum;}int mid = (tr[x].l+tr[x].r)>>1, res = 0;pushdown(x);if(l<=mid) res += querynum(ls, l, r);if(r>mid) res += querynum(rs, l, r);return res;
}void update(int x, int l, int r, int v){if(tr[x].l>=l && tr[x].r<=r){tr[x].sum = (tr[x].r-tr[x].l+1)*v;tr[x].tag = v;return;}int mid = (tr[x].l+tr[x].r)>>1;pushdown(x);if(l<=mid) update(ls, l, r, v);if(r>mid) update(rs, l, r, v);pushup(x);
}bool querypos(int x, int p){if(tr[x].l==tr[x].r && tr[x].l==p){return tr[x].sum == 1;}int mid = (tr[x].l+tr[x].r)>>1;pushdown(x);if(p<=mid) return querypos(ls, p);else return querypos(rs, p);
}bool check(int mid){build(1, 1, n, mid);for(int i=1; i<=m; i++){auto p = opt[i];int cnt1 = querynum(1, p.l, p.r);if(p.op == 0){update(1, p.l, p.r-cnt1, 0);update(1, p.r-cnt1+1, p.r, 1);} else{update(1, p.l, p.l+cnt1-1, 1);update(1, p.l+cnt1, p.r, 0);}}return querypos(1, q);
}int 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];for(int i=1; i<=m; i++)cin>>opt[i].op>>opt[i].l>>opt[i].r;cin>>q;int l = 1, r = n, ans;while(l <= r){int mid = (l+r)>>1;if(check(mid)){l = mid+1;ans = mid;} else{r = mid-1;}}cout<<ans;return 0;
}
P1712 [NOI2016] 区间 权值线段树+离散化+双指针
按排序后的顺序逐一加入区间,然后看看是否有一个点的被覆盖次数 \(\ge m\)。
如果有的话那就统计一下答案,然后将前面加入的按顺序删掉,直到点的覆盖次数 \(<m\)。
注意离散化的一种方式。以及 pushup
操作维护的是最大值(这应该也算权值线段树的一个 trick 吧,区间内被覆盖次数最多的值)。
Code
include <bits/stdc++.h>
using namespace std;
define ll long long
const int N = 1e6+5;
int tot, L[N], R[N];
struct node1{
int len, id;
}a[N];
struct node2{
int v, id;
}p[N];
struct tree{
int l, r;
int sum, tag;
#define ls x<<1
#define rs x<<1|1
}tr[N<<2];
void build(int x, int l, int r){
tr[x].l = l, tr[x].r = r, tr[x].tag = tr[x].sum = 0;
if(l == r) return;
int mid = (l+r)>>1;
build(ls, l, mid);
build(rs, mid+1, r);
}
void pushup(int x){
tr[x].sum = max(tr[ls].sum, tr[rs].sum);
}
void pushdown(int x){
if(!tr[x].tag) return;
tr[ls].sum += tr[x].tag;
tr[rs].sum += tr[x].tag;
tr[ls].tag += tr[x].tag;
tr[rs].tag += tr[x].tag;
tr[x].tag = 0;
}
void update(int x, int l, int r, int v){
if(tr[x].l>=l && tr[x].r<=r){
tr[x].sum += v;
tr[x].tag += v;
return;
}
int mid = (tr[x].l+tr[x].r)>>1;
pushdown(x);
if(l<=mid) update(ls, l, r, v);
if(r>mid) update(rs, l, r, v);
pushup(x);
}
int main(){
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
int n, m; cin>>n>>m;
for(int i=1; i<=n; i++){
int u, v; cin>>u>>v;
a[i] = {v-u, i};
p[++tot] = {u, i};
p[++tot] = {v, i};
}
sort(a+1, a+1+n, [&](node1 x, node1 y){return x.len < y.len;});
sort(p+1, p+1+tot, [&](node2 x, node2 y){return x.v < y.v;});
int num = 0;
p[0].v = -1;
for(int i=1; i<=tot; i++){
if(p[i].v != p[i-1].v)
num++;
int u = p[i].id;
if(!L[u]) L[u] = num;
else R[u] = num;
}
build(1, 1, num);
int ans = INT32_MAX-1, le=0, ri=0;
while(true){
while(tr[1].sum<m && ri<n){
ri++;
update(1, L[a[ri].id], R[a[ri].id], 1);
}
if(tr[1].sum<m) break;
while(tr[1].sum>=m && le<ri){
le++;
update(1, L[a[le].id], R[a[le].id], -1);
}
ans = min(ans, a[ri].len-a[le].len);
}
cout<<(ans==INT32_MAX-1 ? -1 : ans);
return 0;
}
P1856 [IOI1998] [USACO5.5] 矩形周长Picture 扫描线+线段树
依旧先推一篇博客。
关于答案计算:
注意先加边后删边。
对于横边,答案是相邻两次修改的区间覆盖长度差(就是 $tr[root].len $ 的差)全部加起来。即 \(\sum\limits_{i=2}^{e} \lvert {len}_i - {len}_{i-1}\rvert\),其中 \(e\) 是横边的总边数,\(len\) 就是 \(tr[root].len\)。
为什么呢?考虑两个矩形嵌套,然后在前一个矩形的下边的扫描线上的被覆盖的长度,和当前第二个矩形的下边时扫描线上的被覆盖长度的差就是第二个矩形延展出来的长度,因为取一个矩形的上边的时候可能会缩回去,所以要加绝对值。
对于竖边,朴素想法是再从左到右扫一遍。但是可以通过 \(tree[root].num\) 计算。答案是 \(\sum\limits_{i=2}^{e} 2 \cdot num_i \cdot \lvert h_i - h_{i-1} \rvert\),其中 \(e\) 是横边的总边数,\(h\) 是当前边的高度(纵坐标),\(num\) 就是 \(tree[root].num\)。
为什么竖边可以这样计算呢?请看下图:
Code
#include <bits/stdc++.h>
using namespace std;
#define ll long longstruct Tree{int l, r;int sum;//整个区间被整体覆盖了几次(类似lazytag,但不下传)int num;//整个区间被几条互不相交的线段覆盖(比如,[1,2],[4,5]则为2,[1,3],[4,5]则为1(我习惯用闭区间),[1,4],[2,2],[4,4]也为1)int len;//整个区间被覆盖的总长度bool lflag;//左端点是否被覆盖(合并用)bool rflag;//右端点是否被覆盖(合并用)#define ls x<<1#define rs x<<1|1
}tr[100005];
struct Edge{int l, r, h, flag;
}e[10005];int egnum, ans, lst;void add_edge(int l, int r, int h, int f){e[++egnum] = {l, r, h, f};
}void build(int x, int l, int r){tr[x].l = l, tr[x].r = r;if(l == r){tr[x].len = 0;tr[x].num = 0;tr[x].lflag = tr[x].rflag = 0;return;}int mid = (tr[x].l+tr[x].r)>>1;build(ls, l, mid);build(rs, mid+1, r);
}void pushup(int x, int l, int r){if(tr[x].sum){tr[x].num = 1;tr[x].len = r-l+1;tr[x].lflag = tr[x].rflag = 1;} else if(l == r){tr[x].num = 0;tr[x].len = 0;tr[x].lflag = tr[x].rflag = 0;} else{tr[x].len = tr[ls].len+tr[rs].len;tr[x].num = tr[ls].num+tr[rs].num;if(tr[ls].rflag && tr[rs].lflag) tr[x].num--;tr[x].lflag = tr[ls].lflag;tr[x].rflag = tr[rs].rflag;}
}void add(int x, int l, int r, int v){if(tr[x].l>=l && tr[x].r<=r){tr[x].sum += v;pushup(x, tr[x].l, tr[x].r); // 一开始的 build 没有初始化return;}int mid = (tr[x].l+tr[x].r)>>1;if(l<=mid) add(ls, l, r, v);if(r>mid) add(rs, l, r, v);pushup(x, tr[x].l, tr[x].r);
}int main(){ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);int n; cin>>n;int mx = INT32_MIN+1, mn = INT32_MAX-1;for(int i=1; i<=n; i++){int x1, y1, x2, y2; cin>>x1>>y1>>x2>>y2;mx = max({mx, x1, x2});mn = min({mn, x1, x2});add_edge(x1, x2, y1, 1); // 下面的边+1add_edge(x1, x2, y2, -1); // 上面的边-1}if(mn <= 0){for(int i=1; i<=egnum; i++){e[i].l += (-mn+1);e[i].r += (-mn+1);}mx -= mn;} // 负数下标 -> 正数下标sort(e+1, e+egnum+1, [&](Edge a, Edge b){return (a.h==b.h) ? (a.flag>b.flag) : (a.h<b.h);}); // 先加边再删边build(1, 1, mx);for(int i=1; i<=egnum; i++){add(1, e[i].l, e[i].r-1, e[i].flag);// 点坐标变为线段长度会减一while(e[i].h==e[i+1].h && e[i].flag==e[i+1].flag){i++;add(1, e[i].l, e[i].r-1, e[i].flag);}ans += abs(tr[1].len-lst);lst = tr[1].len;ans += tr[1].num*2*(e[i+1].h-e[i].h);}cout<<ans;return 0;
}
CF786B Legacy 线段树优化建图
题目大意:有 \(n\) 个点、\(q\) 次操作。每一种操作为以下三种类型中的一种:
- 操作一:连一条 \(u\to v\) 的有向边,权值为 \(w\)。
- 操作二:对于所有 \(i\in[l,r]\) 连一条 \(u\to i\) 的有向边,权值为 \(w\)。
- 操作三:对于所有 \(i\in[l,r]\) 连一条 \(i\to u\)的有向边,权值为 \(w\)。
求从点 \(s\) 到其他点的最短路。
\(1\leq n,q\leq10^5,1\leq w\leq10^9\)。
再次推荐一篇博客。
感觉这篇博客真得讲的很清晰,就不多写些什么了。其实是菜!
特别注意连边时都变成了 \(x\) 在线段树上的节点 \(leaf[x]\)!!!
包括在跑最短路的时候也一样(比方说起点处理的时候就可能又写错)。
Code
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int D = 5e5;
const int N = 1e5+5;
#define pii pair<ll, int>
#define fi first
#define se secondstruct tree{int l, r;#define ls (x<<1) // 注意这个括号:((#define rs (x<<1|1)
}tr[N<<2];
int leaf[N];struct node{int v;ll w;
};
vector<node> g[1000005];void build(int x, int l, int r){tr[x].l = l, tr[x].r = r;if(l == r){leaf[l] = x;return;}g[x].push_back({ls, 0});g[x].push_back({rs, 0});g[ls+D].push_back({x+D, 0});g[rs+D].push_back({x+D, 0});int mid = (tr[x].l+tr[x].r)>>1;build(ls, l, mid);build(rs, mid+1, r);
}void add(int x, int v, int l, int r, int w, int op){if(tr[x].l>=l && tr[x].r<=r){if(op==2) g[v+D].push_back({x, w});else g[x+D].push_back({v, w});return;}int mid = (tr[x].l+tr[x].r)>>1;if(l<=mid) add(ls, v, l, r, w, op);if(r>mid) add(rs, v, l, r, w, op);
}ll dis[1000005];
bool vis[1000005];void dijkstra(int s){priority_queue<pii, vector<pii>, greater<pii>> Q;memset(dis, 0x3f, sizeof(dis));dis[s] = 0; Q.push({0, s});while(!Q.empty()){int u = Q.top().se; Q.pop();if(vis[u]) continue;vis[u] = true;for(auto [v, w] : g[u]){if(dis[v] > dis[u]+w){dis[v] = dis[u]+w;Q.push({dis[v], v});}}}
}int main(){ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);int n, q, s; cin>>n>>q>>s;build(1, 1, n);for(int i=1; i<=q; i++){int op; cin>>op;if(op == 1){int v, u, w; cin>>v>>u>>w;g[leaf[v]].push_back({leaf[u], w});} else{int v, l, r, w; cin>>v>>l>>r>>w;add(1, leaf[v], l, r, w, op);}}// 叶子节点连边for(int i=1; i<=n; i++){g[leaf[i]].push_back({leaf[i]+D, 0});g[leaf[i]+D].push_back({leaf[i], 0});}dijkstra(leaf[s]);for(int i=1; i<=n; i++)cout<<(dis[leaf[i]]>=0x3f3f3f3f3f3f3f3fll ? -1 : dis[leaf[i]])<<" ";return 0;
}
关于此题还有一个小拓展:考虑一个边集的所有点向另一个边集的所有点连边?
trick:在两边集间建一个虚点,连边 \([l_1, r_1] \stackrel{w}\longrightarrow point \stackrel{0}\longrightarrow [l_2, r_2]\)。这样连的边数会大大减少。
P3372 【模板】线段树 1 动态开点线段树
因为还没怎么理解过动态开点线段树,所以只能先写一波模板了。
因为感觉写的很烂,所以写了很多版,而且这题写完之后发现确实不适合用动态开点。
Code
// #include <bits/stdc++.h>
// using namespace std;
// #define ll long long
// const int N = 1e5+5;// #define ls (tr[x].lson)
// #define rs (tr[x].rson)// ll a[N], pre[N];// struct tree{
// int l, r;
// int lson, rson;
// ll sum, tag;
// tree(){}
// tree(int l_, int r_){
// l=l_; r=r_; lson=rson=0;
// sum=pre[r_]-pre[l_-1];
// }
// }tr[N<<1];
// int ntot, rt;// void pushup(int &x){
// int mid = (tr[x].l+tr[x].r)>>1;
// if(!ls){
// ls = ++ntot;
// tr[ls] = tree(tr[x].l, mid);
// }
// if(!rs){
// rs = ++ntot;
// tr[rs] = tree(mid+1, tr[x].r);
// }
// tr[x].sum = tr[ls].sum+tr[rs].sum;
// }// void pushdown(int &x){
// if(!tr[x].tag) return;
// int mid = (tr[x].l+tr[x].r)>>1;
// if(!ls){
// ls = ++ntot;
// tr[ls] = tree(tr[x].l, mid);
// }
// if(!rs){
// rs = ++ntot;
// tr[rs] = tree(mid+1, tr[x].r);
// }
// tr[ls].sum += (mid-tr[x].l+1)*tr[x].tag;
// tr[rs].sum += (tr[x].r-mid)*tr[x].tag;
// tr[ls].tag += tr[x].tag;
// tr[rs].tag += tr[x].tag;
// tr[x].tag = 0;
// }// void update(int &x, int s, int t, int l, int r, ll v){
// if(!x){
// x = ++ntot;
// tr[x] = tree(s, t);
// }
// if(tr[x].l>=l && tr[x].r<=r){
// tr[x].sum += (tr[x].r-tr[x].l+1)*v;
// tr[x].tag += v;
// return;
// }
// pushdown(x);
// int mid = (tr[x].l+tr[x].r)>>1;
// if(l<=mid) update(ls, s, mid, l, r, v);
// if(r>mid) update(rs, mid+1, t, l, r, v);
// pushup(x);
// }// ll query(int &x, int s, int t, int l, int r){
// if(!x){
// x = ++ntot;
// tr[x] = tree(s, t);
// }
// if(tr[x].l>=l && tr[x].r<=r){
// return tr[x].sum;
// }
// int mid = (tr[x].l+tr[x].r)>>1;
// ll res = 0;
// pushdown(x);
// if(l<=mid) res += query(ls, s, mid, l, r);
// if(r>mid) res += query(rs, mid+1, t, l, r);
// return res;
// }// int main(){
// ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
// int n, m; cin>>n>>m;
// for(int i=1; i<=n; i++){
// cin>>a[i];
// pre[i] = pre[i-1]+a[i];
// }
// for(int i=1; i<=m; i++){
// int op; cin>>op;
// if(op==1){
// int l, r; ll v; cin>>l>>r>>v;
// update(rt, 1, n, l, r, v);
// } else if(op==2){
// int l, r; cin>>l>>r;
// cout<<query(rt, 1, n, l, r)<<"\n";
// }
// }
// return 0;
// }// #include <bits/stdc++.h>
// using namespace std;
// #define ll long long
// const int N = 1e5+5;// ll a[N];// struct tree{
// int ls, rs;
// ll sum, tag;
// }tr[N<<1];
// int tot, rt;// void pushup(int &x){
// tr[x].sum = tr[tr[x].ls].sum+tr[tr[x].rs].sum;
// }// void pushdown(int &x, int l, int r){
// if(!tr[x].tag) return;
// int mid = (l+r)>>1;
// tr[tr[x].ls].sum += (mid-l+1)*tr[x].tag;
// tr[tr[x].rs].sum += (r-mid)*tr[x].tag;
// tr[tr[x].ls].tag += tr[x].tag;
// tr[tr[x].rs].tag += tr[x].tag;
// tr[x].tag = 0;
// }// void update(int &x, int l, int r, int ql, int qr, ll v){
// if(!x) x = ++tot;
// if(l>=ql && r<=qr){
// tr[x].sum += (r-l+1)*v;
// tr[x].tag += v;
// return;
// }
// pushdown(x, l, r);
// int mid = (l+r)>>1;
// if(ql<=mid) update(tr[x].ls, l, mid, ql, qr, v);
// if(qr>mid) update(tr[x].rs, mid+1, r, ql, qr, v);
// pushup(x);
// }// ll query(int &x, int l, int r, int ql, int qr){
// if(!x) return 0;
// if(l>=ql && r<=qr){return tr[x].sum;}
// int mid = (l+r)>>1;
// pushdown(x, l, r);
// ll res = 0;
// if(ql<=mid) res += query(tr[x].ls, l, mid, ql, qr);
// if(qr>mid) res += query(tr[x].rs, mid+1, r, ql, qr);
// return res;
// }// int main(){
// ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
// int n, m; cin>>n>>m;
// for(int i=1; i<=n; i++){
// ll x; cin>>x;
// update(rt, 1, n, i, i, x);
// }
// for(int i=1; i<=m; i++){
// int op; cin>>op;
// if(op==1){
// int l, r; ll v; cin>>l>>r>>v;
// update(rt, 1, n, l, r, v);
// } else if(op==2){
// int l, r; cin>>l>>r;
// cout<<query(rt, 1, n, l, r)<<"\n";
// }
// }
// return 0;
// }// #include <bits/stdc++.h>
// using namespace std;
// #define ll long long
// const int N = 1e5+5;// #define ls (tr[x].lson)
// #define rs (tr[x].rson)// ll a[N];// struct tree{
// int l, r;
// int lson, rson;
// ll sum, tag;
// tree(){}
// tree(int _l, int _r){ l = _l, r = _r; lson = rson = 0; }
// }tr[N<<1];// int ntot, rt;// void pushup(int &x){
// tr[x].sum = tr[ls].sum+tr[rs].sum;
// }// void pushdown(int &x){
// if(!tr[x].tag) return;
// tr[ls].sum += (tr[ls].r-tr[ls].l+1)*tr[x].tag;
// tr[rs].sum += (tr[rs].r-tr[rs].l+1)*tr[x].tag;
// tr[ls].tag += tr[x].tag;
// tr[rs].tag += tr[x].tag;
// tr[x].tag = 0;
// }// void update(int &x, int s, int t, int l, int r, ll v){
// if(!x){
// x = ++ntot;
// tr[x] = tree(s, t);
// }
// if(tr[x].l>=l && tr[x].r<=r){
// tr[x].sum += (tr[x].r-tr[x].l+1)*v;
// tr[x].tag += v;
// return;
// }
// pushdown(x);
// int mid = (tr[x].l+tr[x].r)>>1;
// if(l<=mid) update(ls, s, mid, l, r, v);
// if(r>mid) update(rs, mid+1, t, l, r, v);
// pushup(x);
// }// ll query(int &x, int l, int r){
// if(!x) return 0;
// if(tr[x].l>=l && tr[x].r<=r){
// return tr[x].sum;
// }
// int mid = (tr[x].l+tr[x].r)>>1;
// ll res = 0;
// pushdown(x);
// if(l<=mid) res += query(ls, l, r);
// if(r>mid) res += query(rs, l, r);
// return res;
// }// int main(){
// ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
// int n, m; cin>>n>>m;
// for(int i=1; i<=n; i++){
// ll x; cin>>x;
// update(rt, 1, n, i, i, x);
// }
// for(int i=1; i<=m; i++){
// int op; cin>>op;
// if(op==1){
// int l, r; ll v; cin>>l>>r>>v;
// update(rt, 1, n, l, r, v);
// } else if(op==2){
// int l, r; cin>>l>>r;
// cout<<query(rt, l, r)<<"\n";
// }
// }
// return 0;
// }
P3834 【模板】可持久化线段树 2 离散化+可持久化线段树
可持久化线段树的经典应用:静态区间第 \(k\) 大/小。
因为自己离散化一直理解的不是很透,所以这里多写一点关于离散化的篇幅。
离散化一般都是在值域太大的情况下使用的。目的是记录原数组下标以最后访问原数组的值。因为离散化前后对应的大小关系不变,所以需要先排序。
过程:
- 假设原数组为 \(a\),新建一个数组 \(b\),满足 \(b_i = a_i\)。
- 对数组 \(b\) 排序并去重。注意
unique()
函数最后返回的是第一个重复元素的下标(原理图放在代码后)。可借此求出 \(b\) 数组的大小(同时这个值也是权值线段树的值域上界,代码中为 \(M\))。 - 遍历 \(a\) 数组二分查找求出每个值 \(a_i\) 现在在 \(b\) 数组中的位置。
此时 \(b\) 数组就是原数组去重后的有序数据,\(a\) 数组就是原数据在 \(b\) 数组中的下标。
for(int i=1; i<=n; i++){cin>>a[i];b[i] = a[i];
}
sort(b+1, b+1+n);
int M = unique(b+1, b+1+n)-b-1;
for(int i=1; i<=n; i++)a[i] = lower_bound(b+1, b+1+M, a[i]) - b;
关于动态开点线段树的讲解本蒻只能再推一篇讲解力。
Code
// 依旧写了 2 种版本qwq// #include <bits/stdc++.h>
// using namespace std;
// #define ll long long
// const int N = 2e5+5;// int a[N], b[N];
// int rt[N], cnt;
// int ls[N*20], rs[N*20], sum[N*20];// int update(int x, int l, int r, int pos){
// int nx = ++cnt;
// ls[nx] = ls[x], rs[nx] = rs[x];
// sum[nx] = sum[x]+1;
// if(l == r) return nx;
// int mid = (l+r)>>1;
// if(pos<=mid) ls[nx] = update(ls[x], l, mid, pos);
// else rs[nx] = update(rs[x], mid+1, r, pos);
// return nx;
// }// int query(int x, int y, int l, int r, int k){
// if(l == r) return l;
// int mid = (l+r)>>1;
// int s = sum[ls[y]] - sum[ls[x]];
// if(s>=k) return query(ls[x], ls[y], l, mid, k);
// else return query(rs[x], rs[y], mid+1, r, k-s);
// }// int main(){
// ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
// int n, q; cin>>n>>q;
// for(int i=1; i<=n; i++){
// cin>>a[i];
// b[i] = a[i];
// }
// sort(b+1, b+1+n);
// int M = unique(b+1, b+1+n)-b-1;
// for(int i=1; i<=n; i++)
// a[i] = lower_bound(b+1, b+1+M, a[i]) - b;
// for(int i=1; i<=n; i++){
// rt[i] = update(rt[i-1], 1, M, a[i]);
// }
// while(q--){
// int l, r, k; cin>>l>>r>>k;
// cout<<b[query(rt[l-1], rt[r], 1, M, k)]<<"\n";
// }
// return 0;
// }#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 2e5+5;int a[N], b[N];
int rt[N], cnt;
struct node{int ls, rs;int sum;
}tr[N*20];int newnode(int ls, int rs, int sum){int idx = ++cnt;tr[idx] = {ls, rs, sum};return idx;
}void update(int &x, int prex, int l, int r, int pos){x = newnode(tr[prex].ls, tr[prex].rs, tr[prex].sum+1);if(l == r) return;int mid = (l+r)>>1;if(pos<=mid) update(tr[x].ls, tr[prex].ls, l, mid, pos);else update(tr[x].rs, tr[prex].rs, mid+1, r, pos);
}int query(int x, int y, int l, int r, int k){if(l == r) return l;int mid = (l+r)>>1;int s = tr[tr[y].ls].sum - tr[tr[x].ls].sum;if(s>=k) return query(tr[x].ls, tr[y].ls, l, mid, k);else return query(tr[x].rs, tr[y].rs, mid+1, r, k-s);
}int main(){ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);int n, q; cin>>n>>q;for(int i=1; i<=n; i++){cin>>a[i];b[i] = a[i];}sort(b+1, b+1+n);int M = unique(b+1, b+1+n)-b-1;for(int i=1; i<=n; i++)a[i] = lower_bound(b+1, b+1+M, a[i]) - b;for(int i=1; i<=n; i++){update(rt[i], rt[i-1], 1, M, a[i]);}while(q--){int l, r, k; cin>>l>>r>>k;cout<<b[query(rt[l-1], rt[r], 1, M, k)]<<"\n";}return 0;
}
P4097 【模板】李超线段树 李超线段树
待补+维护凸包。