【学习笔记】线段树应用
【学习笔记】线段树应用
标题用 ##,说明太水啦~
主要是以一些题目为例谈谈线段树的一些拓展用法,感觉线段树很神!
P2146 [NOI2015] 软件包管理器 树剖+线段树
树剖+线段树板子,这种树剖的题只是加了个树剖的壳把它转换为区间问题罢了。至于为什么,这里弱弱的引用神🐟的一张图:
关于各点的 dfn,可以发现两个性质:
- 一条重链上的点的 dfn 连续。
- 一棵子树上的点的 dfn 也连续。
本题中就主要有两个操作:
- 每次安装软件,就把根节点到
软件路径上的值全部变为 。 - 每次卸载软件,就把
以及它的子树的值变为 。
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; // 已经修改的工资总量,每次加入员工时减去front
build(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 序列多次排序:
使用线段树来维护。查询一段区间内的
然后就是最人类智慧的一集:
二分答案
单调性:
假设一下,如果二分的答案是
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] 区间 权值线段树+离散化+双指针
按排序后的顺序逐一加入区间,然后看看是否有一个点的被覆盖次数
如果有的话那就统计一下答案,然后将前面加入的按顺序删掉,直到点的覆盖次数
注意离散化的一种方式。以及 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 扫描线+线段树
依旧先推一篇博客。
关于答案计算:
注意先加边后删边。
对于横边,答案是相邻两次修改的区间覆盖长度差(就是
为什么呢?考虑两个矩形嵌套,然后在前一个矩形的下边的扫描线上的被覆盖的长度,和当前第二个矩形的下边时扫描线上的被覆盖长度的差就是第二个矩形延展出来的长度,因为取一个矩形的上边的时候可能会缩回去,所以要加绝对值。
对于竖边,朴素想法是再从左到右扫一遍。但是可以通过
为什么竖边可以这样计算呢?请看下图:
Code
#include <bits/stdc++.h>
using namespace std;
#define ll long long
struct 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); // 下面的边+1
add_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 线段树优化建图
题目大意:有
- 操作一:连一条
的有向边,权值为 。 - 操作二:对于所有
连一条 的有向边,权值为 。 - 操作三:对于所有
连一条 的有向边,权值为 。
求从点
再次推荐一篇博客。
感觉这篇博客真得讲的很清晰,就不多写些什么了。其实是菜!
特别注意连边时都变成了
包括在跑最短路的时候也一样(比方说起点处理的时候就可能又写错)。
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 second
struct 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:在两边集间建一个虚点,连边
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 离散化+可持久化线段树
可持久化线段树的经典应用:静态区间第
因为自己离散化一直理解的不是很透,所以这里多写一点关于离散化的篇幅。
离散化一般都是在值域太大的情况下使用的。目的是记录原数组下标以最后访问原数组的值。因为离散化前后对应的大小关系不变,所以需要先排序。
过程:
- 假设原数组为
,新建一个数组 ,满足 。 - 对数组
排序并去重。注意unique()
函数最后返回的是第一个重复元素的下标(原理图放在代码后)。可借此求出 数组的大小(同时这个值也是权值线段树的值域上界,代码中为 )。 - 遍历
数组二分查找求出每个值 现在在 数组中的位置。
此时
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 【模板】李超线段树 李超线段树
以下内容大部分摘自OI_wiki。
李超线段树是运用标记永久化思想的线段树。主要用来记录区间中最优线段。
同样是记录值域,所以可以动态开点。
把题意转化为维护如下操作:
- 加入一个一次函数,定义域为
。 - 给定
,求定义域包含 的所有一次函数中,在 处取值最大的那个,如果有多个函数取值相同,选编号最小的。
注意斜率不存在的情况。
假设需要插入一条线段
若该区间无标记,直接打上用该线段更新的标记。
否则,按新线段
具体来说,设当前区间的中点为
如果新线段
若在左端点处
若在右端点处
若在左右端点处
除了这两种情况之外,还有一种情况是
最后将
注意懒标记并不等价于在区间中点处取值最大的线段。为什么呢?考虑下图所示情况:
当我们加入橙色线段时,递归更新了
所以最后查询答案时可以利用标记永久化思想,在包含
Code
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int MOD1 = 39989, MOD2 = 1e9;
const double eps = 1e-9;
#define ls (x<<1)
#define rs (x<<1|1)
#define pdi pair<double, int>
#define fi first
#define se second
struct line{
double k, b;
}ln[100005];
int cnt;
int maxp[160005];
int cmp(double x, double y){
if(x-y>eps) return 1;
if(y-x>eps) return -1;
return 0;
}
double calc(int id, int x){
return ln[id].k*x + ln[id].b;
}
void add(int x0, int y0, int x1, int y1){
cnt++;
if(x0 == x1) // 斜率不存在
ln[cnt].k = 0, ln[cnt].b = max(y0, y1);
else
ln[cnt].k = 1.0*(y1-y0)/(x1-x0), ln[cnt].b = y0-ln[cnt].k*x0;
}
void upd(int x, int l, int r, int u){
int &v = maxp[x];
int mid = (l+r)>>1;
int cmid = cmp(calc(u, mid), calc(v, mid));
if(cmid==1 || (!cmid && u<v)) swap(u, v); // !(-1) = true,经历20min实况
int cl = cmp(calc(u, l), calc(v, l)), cr = cmp(calc(u, r), calc(v, r));
if(cl==1 || (!cl && u<v)) upd(ls, l, mid, u); // 注意这里是交换过的
if(cr==1 || (!cr && u<v)) upd(rs, mid+1, r, u);
}
void update(int x, int l, int r, int ql, int qr, int u){
if(l>=ql && r<=qr){
upd(x, l, r, u);
return;
}
int mid = (l+r)>>1;
if(ql<=mid) update(ls, l, mid, ql, qr, u);
if(qr>mid) update(rs, mid+1, r, ql, qr, u);
}
pdi pmax(pdi x, pdi y){
int c = cmp(x.fi, y.fi);
if(c == 1) return x;
if(c == -1) return y;
return (x.se<y.se) ? x : y;
}
pdi query(int x, int l, int r, int k){
double res = calc(maxp[x], k);
if(l == r) return {res, maxp[x]};
int mid = (l+r)>>1;
if(k<=mid) return pmax({res, maxp[x]}, query(ls, l, mid, k));
else return pmax({res, maxp[x]}, query(rs, mid+1, r, k)); // 标记永久化,上传的时候更新
}
int main(){
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
int n, lastans = 0; cin>>n;
while(n--){
int op; cin>>op;
if(op == 1){
int x0, y0, x1, y1; cin>>x0>>y0>>x1>>y1;
x0 = (x0+lastans-1+MOD1)%MOD1+1;
x1 = (x1+lastans-1+MOD1)%MOD1+1;
y0 = (y0+lastans-1+MOD2)%MOD2+1;
y1 = (y1+lastans-1+MOD2)%MOD2+1;
if(x0 > x1) swap(x0, x1), swap(y0, y1);
add(x0, y0, x1, y1);
update(1, 1, MOD1, x0, x1, cnt);
} else{
int k; cin>>k;
k = (k+lastans-1+MOD1)%MOD1+1;
lastans = query(1, 1, MOD1, k).se;
cout<<lastans<<"\n";
}
}
return 0;
}
待补:维护凸包。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】