首页IT科技线段中的动态问题知识点(【线段树】动态开点)

线段中的动态问题知识点(【线段树】动态开点)

时间2025-06-20 13:51:44分类IT科技浏览4073
导读:使用场景 维护的区间太大以至于 \(4N\ 存不下,通常是权值线段树; 维护的区间下标存在负数;...

使用场景

维护的区间太大以至于 \(4N\) 存不下           ,通常是权值线段树; 维护的区间下标存在负数;

时间复杂度

全部开点                  ,则 \(O(2N - 1)\) 每递归一次      ,最多开点 \(O(\log_N)\)         ,若调用 \(M\) 次                  , \(O(M\log_N)\)

原理

若一段子区间 [L,R] 对应的线段树节点为 cur          ,当不需要递归时     ,就不建点;

当调用 addtag() 时                 ,新建节点            。

注意事项

没有 build 函数; addtag 的节点 cur 要取址; 有负数区间时            , mid = (lt + rt - 1) / 2; 根节点 root 为 \(1\)

代码

#include<bits/stdc++.h> using namespace std; #define lc(x) tree[x].lc #define rc(x) tree[x].rc #define sum(x) tree[x].val #define tag(x) tree[x].tag const int N = 1e5 + 5; int n, m, tot; long long a[N] struct node { long long tag, val; int lc, rc; }tree[N*2]; void addtag(int &cur, int lt, int rt, long long val) //注意取值符 { if(cur == 0) //结点不存在就新建立 cur = ++tot; sum(cur) += (rt - lt + 1) * val; tag(cur) += val; return ; } void pushup(int cur) { sum(cur) = sum(lc(cur)) + sum(rc(cur)); return ; } void pushdown(int cur, int lt, int rt) { if(lt >= rt) return ; int mid = (lt + rt - 1) / 2; addtag(lc(cur), lt, mid, tag(cur)); addtag(rc(cur), mid+1, rt, tag(cur)); tag(cur) = 0; return ; } void update(int cur, int lt, int rt, int qx, int qy, long long val) { if(qy < lt || qx > rt) //不归cur管 return ; if(qx <= lt && rt <= qy) //cur管辖的区间要全部修改   ,直接打懒标记 { addtag(cur, lt, rt, val); return ; } pushdown(cur, lt, rt); int mid = (lt + rt - 1) / 2; update(lc(cur), lt, mid, qx, qy, val); update(rc(cur), mid+1, rt, qx, qy, val); pushup(cur); return ; } long long query(int cur, int lt, int rt, int qx, int qy) //结点            、管辖的区间范围                 、询问的区间范围 { if(qy < lt || qx > rt) return 0; if(qx <= lt && rt <= qy) return sum(cur); pushdown(cur, lt, rt); int mid = (lt + rt - 1) / 2; return query(lc(cur), lt, mid, qx, qy) + query(rc(cur), mid+1, rt, qx, qy); } int main() { cin >> n >> m; int root = 1; //根结点编号 tot = 1; //总结点数量 for(int i = 1; i <= n; i++) { int x; cin >> x; update(root, 1, n, i, i, x); } for(int i = 1; i <= m; i++) { int opt, x, y, val; cin >> opt >> x >> y; if(opt == 1) { cin >> val; update(root, 1, n, x, y, val); //结点编号      、管辖的左右边界         、修改的左右边界                 、修改的值 } else cout << query(root, 1, n, x, y) << "\n"; //结点编号         、管辖的左右边界      、询问的左右边界 } return 0; }

例题

Luogu P3372

Luogu P2781

Luogu P5459

P5459题解

简要题意

\(n\) 个数                 ,求有多少个区间的和在 \(L\)\(R\) 之间                 。

思路

本题是求方案树               ,由题面有关数的和可以得知我们需要一种值域线段树,

注意到 \(L\)\(R\) 的范围较大              ,所以需要用动态开点解决此类问题                  ,

我们可以先建一个前缀和   ,自然前缀和 res[] 需要满足 L <= res[j] - res[i] <= R            ,可转化为 res[j] - R <= res[i] <= res[j] - L                   ,

于是      ,可以以每个前缀为节点        ,用线段树维护在一个区间中的方案数                  ,

这道题便做完了      。

AC 代码 #include<bits/stdc++.h> using namespace std; #define lc(x) tree[x].lc #define rc(x) tree[x].rc #define sum(x) tree[x].val #define tag(x) tree[x].tag const int N = 5e6 + 5; const long long M = 1e10 + 5; int n, m, tot; long long a[N], res[N]; struct node { long long tag, val; int lc, rc; }tree[N*2]; void addtag(int &cur, int lt, int rt, long long val) //注意取值符 { if(cur == 0) //结点不存在就新建立 cur = ++tot; sum(cur) += (rt - lt + 1) * val; tag(cur) += val; return ; } void pushup(int cur) { sum(cur) = sum(lc(cur)) + sum(rc(cur)); return ; } void pushdown(int cur, long long lt, long long rt) { if(lt >= rt) return ; long long mid = (lt + rt - 1) / 2; addtag(lc(cur), lt, mid, tag(cur)); addtag(rc(cur), mid+1, rt, tag(cur)); tag(cur) = 0; return ; } void update(int cur, long long val, int add, long long L = -M, long long R = M) { if(val < L || val > R) return ; if(L == R) { addtag(cur, L, R, add); return ; } pushdown(cur, L, R); long long mid = (L + R) >> 1; update(lc(cur), val, add, L, mid); update(rc(cur), val, add, mid+1, R); pushup(cur); return ; } long long query(int cur, long long ql, long long qr, long long L = -M, long long R = M) { if(qr < L || ql > R) return 0; if(ql <= L && qr >= R) return sum(cur); pushdown(cur, L, R); long long mid = (L + R) >> 1; return query(lc(cur), ql, qr, L, mid) + query(rc(cur), ql, qr, mid + 1, R); } signed main() { int ri, le; cin >> n >> le >> ri; int root = 1; tot = 1; for(int i = 1; i <= n; i++) { int x; cin >> x; res[i] = res[i - 1] + x; } long long ans = 0; update(root, res[0], 1); for (int i = 1; i <= n; ++i) { ans += query(root, res[i] - ri, res[i] - le); update(root, res[i], 1); } cout << ans; return 0; }

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
vue简单聊天室(VUE实现Web端多人语音视频聊天)