线段树
线段树
普通线段树
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 4e5 + 5;
int n;
LL a[N];
#define ls u << 1
#define rs u << 1 | 1
#define mid (l + r >> 1)
#define segroot int u = 1, int l = 1, int r = n
#define lson ls, l, mid
#define rson rs, mid + 1, r
LL sum[N], tag[N], len[N];
void up(int u){ sum[u] = sum[ls] + sum[rs]; }
void bld(segroot){
len[u] = r - l + 1;
if(l == r) return sum[u] = a[l], void();
bld(lson), bld(rson), up(u);
}
void down(int u, LL x){ tag[u] += x, sum[u] += x * len[u]; }
void down(int u) { down(ls, tag[u]), down(rs, tag[u]), tag[u] = 0; }
void mdf(int ql, int qr, LL x, segroot){
if(l > qr or r < ql) return ;
if(l >= ql and r <= qr) return down(u, x);
down(u), mdf(ql, qr, x, lson), mdf(ql, qr, x, rson), up(u);
}
LL qry(int ql, int qr, segroot){
if(l > qr or r < ql) return 0;
if(l >= ql and r <= qr) return sum[u];
down(u); return qry(ql, qr, lson) + qry(ql, qr, rson);
}
signed main(){
ios::sync_with_stdio(NULL); cin.tie(nullptr), cout.tie(nullptr);
int q; cin>>n>>q;
for(int i = 1; i <= n; i ++) cin>>a[i];
bld();
while(q --){
int op, x, y; LL v; cin>>op>>x>>y;
if(op == 1) cin>>v, mdf(x, y, v);
else cout<<qry(x, y)<<'\n';
}
return 0;
}
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 5;
int n, q, m;
int a[N];
namespace segtree{
#define ls (u<<1)
#define rs (u<<1|1)
#define mid (l+r>>1)
#define segroot int u=1,int l=1,int r=n
#define lson ls,l,mid
#define rson rs,mid+1,r
struct { long long len,sum,add,mul; } t[N*4];
void up(int u) { t[u].sum = (t[ls].sum + t[rs].sum) % m; }
void bld(segroot) {
t[u].mul = 1, t[u].len = r-l+1;
if( l == r ) { cin>>t[u].sum; return; }
bld(lson), bld(rson), up(u);
}
void add(int u,long long x) {
t[u].sum = (t[u].sum + t[u].len * x) % m,
t[u].add = (t[u].add + x) % m;
}
void mul(int u,long long x) {
t[u].sum = t[u].sum * x % m,
t[u].mul = t[u].mul * x % m,
t[u].add = t[u].add * x % m;
}
void down(int u) {
if( t[u].mul != 1 ) mul(ls,t[u].mul), mul(rs,t[u].mul), t[u].mul = 1;
if( t[u].add ) add(ls,t[u].add), add(rs,t[u].add), t[u].add = 0;
}
long long qry(int ql,int qr,segroot) {
if( qr < l || r < ql ) return 0;
if( ql <= l && r <= qr ) return t[u].sum;
down(u);
return (qry(ql,qr,lson) + qry(ql,qr,rson)) % m;
}
void add(int ql,int qr,long long x,segroot) {
if( qr < l || r < ql ) return;
if( ql <= l && r <= qr ) return add(u,x);
down(u), add(ql,qr,x,lson), add(ql,qr,x,rson), up(u);
}
void mul(int ql,int qr,long long x,segroot) {
if( qr < l || r < ql ) return;
if( ql <= l && r <= qr ) return mul(u,x);
down(u), mul(ql,qr,x,lson), mul(ql,qr,x,rson), up(u);
}
}
using namespace segtree;
signed main(){
scanf("%lld%lld%lld", &n, &q, &m);
bld();
while(q --){
int op, x, y;
scanf("%lld%lld%lld", &op, &x, &y);
if(op == 1){
int k;
scanf("%lld", &k);
mul(x, y, k);
} else if(op == 2){
int k;
scanf("%lld", &k);
add(x, y, k);
} else {
printf("%lld\n", qry(x, y));
}
}
return 0;
}
动态开点线段树
我们发现,又是线段树的很多节点都是无用的,于是我们考虑动态开点,节点按需索取,这样就可以节省一定的空间,防止出现 MLE 的情况。
扫描线
扫描线,就是离线下来所有询问,从小到大扫描下标,并在进行线段树上修改,查询操作。
线段树合并 & 线段树分裂
首先,合并、分裂线段树所消耗的线段树一定很多,所以我们需要动态开点。
线段树合并
对于线段树的合并,我们直接将线段树上位于同一个位置的节点合并起来即可,简单。
int merge(int v, segroot){
if(!u) return v;
if(!v) return u;
sum[u] += sum[v]; // 直接合并
ls[u] = merge(ls[v], ls[u]); // 合并左子树
rs[u] = merge(rs[v], rs[u]); // 合并右子树
return u;
}
线段树分裂
一种类似于二分的方式:
我们考虑将线段树 \([1, n]\) 分成 \([1, x]\) 和 \([x + 1, n]\)。
加入我们现在在节点 \((u, [l, r])\),我们判断 \(mid\) 与 \(x\) 的关系。
若 \(x \le mid\) 则我们要分裂左子树,并将左子树分裂后的右子树作为 \(u\) 的左子树。
若 \(x > mid\) 则我们要分裂右子树,并将右子树分裂后的左子树作为 \(u\) 的右子树。
我们发现,如果直接连上,线段树的层数会紊乱,所以我们要补上新节点,维护层数恒定。
pair<int, int> split(int x, segroot){
if(l == r) return {u, 0};
if(mid >= x){
auto tmp = split(x, lson); // 分离
ls[u] = tmp.second;
int nd = ++ idx;
ls[nd] = tmp.first; up(nd); // 补上新节点
up(u);
return {nd, u};
}
auto tmp = split(x, rson);
rs[u] = tmp.second;
int nd = ++ idx;
rs[nd] = tmp.first;
ls[nd] = ls[u], ls[u] = 0;
up(nd);
up(u);
return {nd, u};
}
最终成品代码:
struct segtree{
LL sum[N * 18];
int ls[N * 18], rs[N * 18];
int idx;
#define mid (l + r >> 1)
#define segroot int &u, int l = 1, int r = n
#define lson ls[u], l, mid
#define rson rs[u], mid + 1, r
void up(int u) { sum[u] = sum[ls[u]] + sum[rs[u]]; }
void add(int x, int v, segroot){
if(l > x || r < x) return ;
if(!u) u = ++ idx;
if(l == r) return sum[u] += v, void();
add(x, v, lson), add(x, v, rson), up(u);
}
LL qry(int ql, int qr, segroot){
if(!u || l > qr || r < ql) return 0;
if(l >= ql && r <= qr) return sum[u];
return qry(ql, qr, lson) + qry(ql, qr, rson);
}
int find(int k, segroot){
if(!u || sum[u] < k) return -1;
if(l == r) return l;
if(sum[ls[u]] >= k) return find(k, lson);
else return find(k - sum[ls[u]], rson);
}
int merge(int v, segroot){
if(!u) return v;
if(!v) return u;
sum[u] += sum[v];
ls[u] = merge(ls[v], ls[u]);
rs[u] = merge(rs[v], rs[u]);
return u;
}
pair<int, int> split(int x, segroot){
if(l == r) return {u, 0};
if(mid >= x){
auto tmp = split(x, lson);
ls[u] = tmp.second;
int nd = ++ idx;
ls[nd] = tmp.first; up(nd);
up(u);
return {nd, u};
}
auto tmp = split(x, rson);
rs[u] = tmp.second;
int nd = ++ idx;
rs[nd] = tmp.first;
ls[nd] = ls[u], ls[u] = 0;
up(nd);
up(u);
return {nd, u};
}
};
线段树分治
实际上应该叫时间分治。
给定一个 \(n\) 的点的图,在第\([l_i, r_i]\) 的时间内有边 \([u_i, v_i]\),询问每个时刻内这个图是否是二分图(或者有其他情况)。
我们这里与二分图有关的不在阐述,只说什么是线段树分治。
我们发现,加边好说,但减边不好做,因为我们无法在一个二分图上快速地判断是否合法。
于是,我们对于时间轴建一颗线段树。
对于每一时间内的每一条边,我们将其加到线段树上,线段树上将会正好将其分成 \(\log k\) 块,\(k = r - l + 1\)。
接下来,我们递归线段树即可求解。
总时间复杂度为 \(\mathcal O(n \log n)\)。
具体代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int n, m, k;
int fa[N << 1], sz[N << 1];
#define ls u << 1
#define rs u << 1 | 1
#define mid (l + r >> 1)
#define segroot int u = 1, int l = 1, int r = k
#define lson ls, l, mid
#define rson rs, mid + 1, r
int find(int x){ return x == fa[x] ? x : find(fa[x]); }
vector<pair<int, int>> tr[N << 2];
void add(int ql, int qr, int x, int y, segroot){ // 加边 [ql, qr] x -> y
if(l > qr || r < ql) return ;
if(l >= ql && r <= qr) return tr[u].push_back({x, y});
add(ql, qr, x, y, lson), add(ql, qr, x, y, rson);
}
int cnt = 0;
void dfs(segroot){
int k = 0;
stack<array<int, 5>> stk;
for(auto it : tr[u]){
int x = it.first, y = it.second;
int uu = find(x), vv = find(y);
if(fa[uu] == fa[vv]) { cnt ++, k ++; continue; }
int un = find(x + n), vn = find(y + n); // 这里我们使用扩展域并查集维护二分图
if(sz[uu] < sz[vn]){
stk.push({uu, fa[uu], sz[uu], vn, sz[vn]});
fa[uu] = vn; sz[vn] += sz[uu];
} else {
stk.push({vn, fa[vn], sz[vn], uu, sz[uu]});
fa[vn] = uu; sz[uu] += sz[vn];
}
if(sz[un] < sz[vv]){
stk.push({un, fa[un], sz[un], vv, sz[vv]});
fa[un] = vv; sz[vv] += sz[un];
} else {
stk.push({vv, fa[vv], sz[vv], un, sz[un]});
fa[vv] = un; sz[un] += sz[vv];
}
} // 加边
if(l == r){
puts(cnt == 0 ? "Yes" : "No");
while(!stk.empty()) { auto tp = stk.top(); fa[tp[0]] = stk.top()[1]; sz[tp[0]] = tp[2]; sz[tp[3]] = tp[4]; stk.pop();}
cnt -= k;
return ;
}
dfs(lson);
dfs(rson);
while(!stk.empty()) { auto tp = stk.top(); fa[tp[0]] = stk.top()[1]; sz[tp[0]] = tp[2]; sz[tp[3]] = tp[4]; stk.pop();} // 删边
cnt -= k;
}
int main(){
ios::sync_with_stdio(NULL); cin.tie(nullptr), cout.tie(nullptr);
cin>>n>>m>>k;
for(int i = 1; i <= n; i ++) fa[i] = i, fa[i + n] = i + n, sz[i] = sz[i + n] = 1;
for(int i = 1, x, y, l, r; i <= m; i ++){
cin>>x>>y>>l>>r;
l ++;
add(l, r, x, y);
}
dfs(1);
return 0;
}
吉司机线段树
给你一个序列,支持一下操作:
- 区间加
- 区间取 \(\min\)
- 求区间和
- 求区间最大值
- 求区间历史最大值
思路
发现:这里要求的是区间历史最大值,如果仅用普通的线段树维护,必然是无法做到的,于是就有了吉司机线段树 SBT。
我们先考虑怎么维护历史最大值。
一个显然的想法是,我们可以再开一个数组 hmx[N << 2] 维护区间历史最大值,每次更新的时候都用当前的 \(\max\) 更新 hmx。
但是我们会发现,线段树保持时间复杂度的秘诀在于懒标记,但是有时,我们的懒标记还没来得及下传,就会被覆盖修改,这就造成了子节的 hmx 并没有被这个点历史最大值更新。
于是我们可以考虑再开一个 tag2 记录区间历史最大懒标记,用来下传更新子节点的历史最大值。
但是我们发现,这样的做法并不能很好地实现区间取 \(\min\) 操作。
于是我们考虑将区间取 \(min\) 转化为区间加。
设当前区间 \([l, r]\) 将要对 \(v\) 取 \(\min\),这就相当于,我们将区间内所有 \(> v\) 的数都变成 \(v\)。形式化地,\(\forall x \in [l,r]: a_x += v - a_x\)。
那么我们接下来还是无法实现,原因在于,我们无法有效率地找到我们的 \(x > v\)。
下面有一个神奇做法:
我们将最大值同非最大值划分出来。
我们不仅维护最大值 max 同时维护次大值 second_max(下文统一写作 se)。对 \(v\) 进行分类讨论:
- \(v \ge mx\):所有数均不超过 \(v\),则该区间无需修改。
- \(se < v < mx\):只需将最大值修改为 \(v\),其余不变,需要打标记。
- \(v \le se\):不止最大值更新,此时则递归到子节点。
这种方式,我们就可以完成区间取 \(\min\) 的工作,总时间复杂度为 \(\mathcal O(n \log^2n)\)。
显然,这颗线段树的时间复杂度分析并不显然,下面势能分析:
时间复杂度势能分析
我们定义一个节点有区分度当且仅当 \(mx > se\),即有严格的最大值和次大值之分,则我们认为这个节点携带了 $1 $ 点势能。
整棵树的势能 \(\Phi = 所有 有区分度 的节点总数\)。
势能分析:
只考虑区间取 \(\min\)(仅此时间复杂度不同于普通线段树),分三种情况:
- \(mx \le v\):没有修改,势能不变。
- \(se < v < mx\):只有最大值降低到 \(v\),是能仍然不变。
- \(se \le v\):此时会有可能递归下去,使得 \(se = mx\),此时势能 \(\Phi\) 减少 \(1\)。
势能增加发生在下传里,每次下传有几率使无区分变成有区分,使 \(\Phi + 1\)。
初始状态下,\(\Phi \le n \log n\)。
势能增加最多每次 down \(\mathcal O(\log n)\) 层,每层时间 \(O(1)\),故总增加量为 \(\mathcal O(q \log n)\)。
因此:
总递归深度 \(\le\) 初始势能 \(+\) 增加的势能 \(+\) 普通线段树。
即总时间复杂度 \(\approx \mathcal O((n + q)\log^2 n)\)。
代码实现
我们需要维护的东西:
int len[N << 2], cnt[N << 2]; // 区间长度、最大值数量(便于批次修改)
LL sum[N << 2], mx[N << 2], se[N << 2], hmx[N << 2]; // 和、max、second_max、历史 max
LL atag[N << 2], btag[N << 2], hatag[N << 2], hbtag[N << 2]; // 最大值标记、非最大值标记、历史最大值标记、非历史最大值标记
上传合并:
inline void up(int u){
sum[u] = sum[ls] + sum[rs], mx[u] = max(mx[ls], mx[rs]), hmx[u] = max(hmx[ls], hmx[rs]); // 普通合并
if(mx[ls] == mx[rs]) cnt[u] = cnt[ls] + cnt[rs], se[u] = max(se[ls], se[rs]);
else if(mx[ls] > mx[rs]) cnt[u] = cnt[ls], se[u] = max(se[ls], mx[rs]);
else cnt[u] = cnt[rs], se[u] = max(mx[ls], se[rs]); // 针对次大值的合并
}
建树:
inline void bld(segroot){
len[u] = r - l + 1;
if(l == r) return cin>>sum[u], mx[u] = hmx[u] = sum[u], cnt[u] = 1, se[u] = -inf, void();
bld(lson), bld(rson), up(u);
}
\(\boxed{\textbf{标记下传}}\):
inline void down(int u, LL a, LL b, LL ha, LL hb){
sum[u] += 1ll * a * cnt[u] + 1ll * b * (len[u] - cnt[u]); // 分别更新最大次大值和
hmx[u] = max(hmx[u], mx[u] + ha); // 历史最大值
mx[u] += a; // 当前最大值
if(se[u] != -inf) se[u] += b; // 次大值
hatag[u] = max(hatag[u], atag[u] + ha), hbtag[u] = max(hbtag[u], btag[u] + hb);
atag[u] += a, btag[u] += b; // 更新标记
}
inline void down(int u){
int mxu = max(mx[ls], mx[rs]);
if(mx[ls] == mxu) down(ls, atag[u], btag[u], hatag[u], hbtag[u]); // 下传最大值
else down(ls, btag[u], btag[u], hbtag[u], hbtag[u]); // 下传次大值
if(mx[rs] == mxu) down(rs, atag[u], btag[u], hatag[u], hbtag[u]); // 下传最大值
else down(rs, btag[u], btag[u], hbtag[u], hbtag[u]); // 下传次大值
atag[u] = btag[u] = hatag[u] = hbtag[u] = 0; // 清空标记
}
区间加:
inline void add(int ql, int qr, int x, segroot){
if(qr < l || ql > r) return ;
if(l >= ql && r <= qr) return down(u, x, x, x, x);
down(u), add(ql, qr, x, lson), add(ql, qr, x, rson), up(u);
}
\(\boxed{\textbf{区间取最小值}}\):
inline void mdf(int ql, int qr, int x, segroot){
if(qr < l || ql > r || mx[u] <= x) return ; // mx[u] <= x 没有影响,无需修改
if(l >= ql && r <= qr && se[u] < x) return down(u, x - mx[u], 0, x - mx[u], 0); // 只对最大值有影响
down(u), mdf(ql, qr, x, lson), mdf(ql, qr, x, rson), up(u);
}
最后的板子:
struct STB{
#define ls (u << 1)
#define rs (u << 1 | 1)
#define mid (l + r >> 1)
#define segroot int u = 1, int l = 1, int r = n
#define lson ls, l, mid
#define rson rs, mid + 1, r
int len[N << 2], cnt[N << 2];
LL sum[N << 2], mx[N << 2], se[N << 2], hmx[N << 2];
LL atag[N << 2], btag[N << 2], hatag[N << 2], hbtag[N << 2];
inline void up(int u){
sum[u] = sum[ls] + sum[rs], mx[u] = max(mx[ls], mx[rs]), hmx[u] = max(hmx[ls], hmx[rs]);
if(mx[ls] == mx[rs]) cnt[u] = cnt[ls] + cnt[rs], se[u] = max(se[ls], se[rs]);
else if(mx[ls] > mx[rs]) cnt[u] = cnt[ls], se[u] = max(se[ls], mx[rs]);
else cnt[u] = cnt[rs], se[u] = max(mx[ls], se[rs]);
}
inline void bld(segroot){
len[u] = r - l + 1;
if(l == r) return cin>>sum[u], mx[u] = hmx[u] = sum[u], cnt[u] = 1, se[u] = -inf, void();
bld(lson), bld(rson), up(u);
}
inline void down(int u, LL a, LL b, LL ha, LL hb){
sum[u] += 1ll * a * cnt[u] + 1ll * b * (len[u] - cnt[u]);
hmx[u] = max(hmx[u], mx[u] + ha);
mx[u] += a;
if(se[u] != -inf) se[u] += b;
hatag[u] = max(hatag[u], atag[u] + ha), hbtag[u] = max(hbtag[u], btag[u] + hb);
atag[u] += a, btag[u] += b;
}
inline void down(int u){
int mxu = max(mx[ls], mx[rs]);
if(mx[ls] == mxu) down(ls, atag[u], btag[u], hatag[u], hbtag[u]);
else down(ls, btag[u], btag[u], hbtag[u], hbtag[u]);
if(mx[rs] == mxu) down(rs, atag[u], btag[u], hatag[u], hbtag[u]);
else down(rs, btag[u], btag[u], hbtag[u], hbtag[u]);
atag[u] = btag[u] = hatag[u] = hbtag[u] = 0;
}
inline void add(int ql, int qr, int x, segroot){
if(qr < l || ql > r) return ;
if(l >= ql && r <= qr) return down(u, x, x, x, x);
down(u), add(ql, qr, x, lson), add(ql, qr, x, rson), up(u);
}
inline void mdf(int ql, int qr, int x, segroot){
if(qr < l || ql > r || mx[u] <= x) return ;
if(l >= ql && r <= qr && se[u] < x) return down(u, x - mx[u], 0, x - mx[u], 0);
down(u), mdf(ql, qr, x, lson), mdf(ql, qr, x, rson), up(u);
}
inline LL qrysum(int ql, int qr, segroot){
if(qr < l || ql > r) return 0;
if(l >= ql && r <= qr) return sum[u];
down(u); return qrysum(ql, qr, lson) + qrysum(ql, qr, rson);
}
inline LL qrymx(int ql, int qr, segroot){
if(qr < l || ql > r) return -inf;
if(l >= ql && r <= qr) return mx[u];
down(u); return max(qrymx(ql, qr, lson), qrymx(ql, qr, rson));
}
inline LL qryh(int ql, int qr, segroot){
if(qr < l || ql > r) return -inf;
if(l >= ql && r <= qr) return hmx[u];
down(u); return max(qryh(ql, qr, lson), qryh(ql, qr, rson));
}
} T;

浙公网安备 33010602011771号