首页 > 其他分享 >【Trick】标记永久化

【Trick】标记永久化

时间:2024-02-09 09:04:35浏览次数:25  
标签:标记 int Trick 修改 永久化 tag 区间 节点

1. 理论

线段树使用来维护区间信息的数据结构。回想一下,是否还记得线段树的 pushdown 操作。

在区间修改区间查询中,由于区间修改时信息不一定能传达到位,需要使用 lazy tag 将修改信息打在非叶子节点上(其实可以不用,但时间复杂度错误)。这样一来,当查询的区间在其子区间时,可以把打在当前节点的标记信息下传。便可以正确的维护区间操作。

设想一下,每次查询时都需要将标记下传。那么常数是不是会一丢丢大....

标记永久化横空出世!

2. 原理

标记永久化。顾名思义,不管你如何操作,标记从始至终都不会动。

记 \(val\) 为节点维护的值,\(tag\) 为节点标记。步骤如下:

  1. 区间修改:除线段树上的修改区间,将包含修改区间的所有节点 \(val\) 修改。\(tag\) 打在线段树上的修改区间。

  2. 区间查询:除线段树上的查询区间,将包含修改区间的所有节点 \(tag\) 累计。与 \(val\) 一起在线段树上的查询区间统计答案。

以区间加区间查为例:

image

最开始的线段树。

区间 \([1,4]\) 加 \(1\)。

image

标记打在 \([1,4]\) 区间,包含修改区间的所有节点(\([1,4]\)节点)加 \((4-1+1)\times 1\)。

区间 \([2,4]\) 加 \(2\)。

image

标记打在 \([3,4],[2,2]\) 区间,包含修改区间的所有节点(\([1,4],[1,2],[3,4],[2,2]\)节点)加相应的贡献。

查询 \([2,4]\) 区间。

兵分两路 \([1,4]\to [2,2],[1,4]\to [3,4]\)。

  1. \([1,4]\to [2,2]\),累加 \(tag\) 贡献 \(1+0=1\),累计答案 \(3+(2-2+1)\times 1=4\);

  2. \([1,4]\to [3,4]\),累加 \(tag\) 贡献 \(1\),累计答案 \(6+(4-3+1)\times 1=8\);

答案为 \(4+8=12\)。

3. 正确性证明

每个节点的 \(val\) 还是维护 \([l,r]\) 的贡献,不过每次在修改之后都会实时更新贡献(修改不完整的区间)。

而 \(tag\) 的作用则是修改整块区间,并将 \([l,r]\) 整块区间标记 \(tag\) 的贡献。(修改完整的区间)。

修改不用多说。

查询的时候将包含查询区间的完整区间与不完整区间都统计到了,自然就是要的答案。

关于算重:显然不会,每次标记打在完整区间,修改值在不完整区间。统计也是分开统计,不能也不会弄混。

4. 代码实现

P3372 【模板】线段树 1 为例。

#include <bits/stdc++.h>
#define int long long
#define H 19260817
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define MOD 1000003
#define mod 1000000007
#define ls p<<1
#define rs p<<1|1

using namespace std;

namespace Read {
  template <typename T>
  inline void read(T &x) {
    x=0;T f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x*=f;
  }
  template <typename T, typename... Args>
  inline void read(T &t, Args&... args) {
    read(t), read(args...);
  }
}

using namespace Read;

void print(int x){
  if(x<0){putchar('-');x=-x;}
  if(x>9){print(x/10);putchar(x%10+'0');}
  else putchar(x+'0');
  return;
}

const int N = 1e5 + 10;

struct Node {
  int l, r, val, add;
} t[N << 2];

int n, m;

void pushup(int p) {
  t[p].val = t[ls].val + t[rs].val;
}

void build(int p, int l, int r) {
  t[p].l = l, t[p].r = r;
  if(l == r) {
    read(t[p].val);
    return ;
  }
  int mid = (l + r) >> 1;
  build(ls, l, mid);
  build(rs, mid + 1, r);
  pushup(p);
}

void upd(int p, int l, int r, int k) {
  t[p].val += (min(t[p].r, r) - max(t[p].l, l) + 1) * k;
  if(l <= t[p].l && t[p].r <= r) {
    t[p].add += k;
    return ;
  }
  int mid = (t[p].l + t[p].r) >> 1;
  if(l <= mid) upd(ls, l, r, k);
  if(r > mid) upd(rs, l, r, k);
}

int qry(int p, int l, int r, int Tag) {
  if(l <= t[p].l && t[p].r <= r) {
    return t[p].val + (min(t[p].r, r) - max(t[p].l, l) + 1) * Tag;
  }
  int mid = (t[p].l + t[p].r) >> 1, ans = 0;
  Tag += t[p].add;
  if(l <= mid) ans += qry(ls, l, r, Tag);
  if(r > mid) ans += qry(rs, l, r, Tag);
  return ans;
}

signed main() {
  read(n, m);
  build(1, 1, n);
  while(m--) {
    int op, x, y, k;
    read(op);
    if(op == 1) {
      read(x, y, k);
      upd(1, x, y, k);
    } else {
      read(x, y);
      cout << qry(1, x, y, 0) << '\n';
    }
  }
  return 0;
}

注意细节:

  1. 不需要 pushup,每次区间在递归时就改好了;

  2. 统计答案/贡献时记得去区间交(每次递归到的区间不一定是包含修改/查询区间的区间)。

5. 试用范围

\(tag\) 不需考虑先后顺序以及满足交换律

其他情况,标记永久化 一律寄掉!!

标签:标记,int,Trick,修改,永久化,tag,区间,节点
From: https://www.cnblogs.com/Daniel-yao/p/18012291

相关文章

  • Markdown:简洁高效的文本标记语言
    引言在当今信息爆炸的时代,我们需要一种简洁、高效的文本标记语言来排版和发布内容。Markdown应运而生,它是一种轻量级的文本标记语言,以其简单易学、易读易写的特点,成为了广大写作者的首选工具。本文将介绍Markdown的语法优缺点,以及它可以解决的问题和应用领域。Markdown在线......
  • js 插入标记
    HTML5将IE发明的innerHTML和outerHTML纳入了标准,但还有两个属性没有入选。这两个剩下的属性是innerText和outerText。innerText属性innerText属性对应元素中包含的所有文本内容,无论文本在子树中哪个层级。在用于读取值时,innerText会按照深度优先的顺序将子树中所有文......
  • tricks I
    1.P2824排序碰见这种只有最后有一个查询的问题我们可以考虑二分最后的答案。具体地,对于当前\(mid\),把所有小于\(mid\)的设为\(0\),其他的设为\(1\)。此时我们只需要维护最后的位置是否大于\(mid\)就好。那么每次升降序的排序就很好办了。我们用线段树维护一个区间和,也......
  • 描述文件错误:如何屏蔽 iOS 软件自动更新,去除更新通知和标记
    描述文件错误:如何屏蔽iOS软件自动更新,去除更新通知和标记适用于iOS、iPadOS和watchOS,即iPhone、iPad和AppleWatch通用请访问原文链接:https://sysin.org/blog/disable-ios-update/,查看最新版。原创作品,转载请保留出处。作者主页:sysin.org如何禁用iPhone、iPad和A......
  • Matlab-修改标记点间隔Maker,MarkerIndices
    xx=10;maker_idx_1=1:ceil(length(a)/xx):length(a);maker_idx_1(length(maker_idx_1)+1)=length(a);maker_idx_2=1:ceil(length(c)/xx):length(c);maker_idx_2(length(maker_idx_2)+1)=length(c);maker_idx_3=1:ceil(length(e)/xx):length(e);maker_idx_3(length(ma......
  • go gin 必须使用 dive 标记,它告诉 required 校验 深入到 slice、array 这样的子结
    packagemainimport( "fmt" "net/http" "github.com/gin-gonic/gin")typeuserstruct{ Namestring`json:"name"binding:"required"` Emailstring`json:"email"binding:"required,email"`......
  • 最近见到的 trick 汇总
    见到啥写啥吧qwq历年省选/NOI/NOIP/其他官方考试已经标出。OEIS把样例粘贴到OEIS上。www.oeis.org怎么你了。CF1916H1/H2数学容斥原理求解问题。\[|S_1\cupS_2\cup\cdots\cupS_n|=\sum_{i=1}^n|S_i|+\sum_{k=2}^n(-1)^{k-1}\sum_{1\leqi_1<i_2<\cdots<i_k\leqn}|S......
  • Essay - OI tricks
    ......
  • trick 集合
    trick集合1.基础判断是否\(\foralli\)有\(x<a_i\):转化为是否\(x<\min(a_i)\)。大于类似。P9868&题解,ABC223F&题解括号序列:是括号序列的条件:总共的左括号和右括号数量相等;任意前缀的左括号数量\(\ge\)右括号数量。若将序列中左括号看作......
  • Markdown标记语言
    Markdown标记语言标题"#"开头加空格是一级标题“##”两个#开头就是二级标题以此类推。字体加粗字体斜体斜体加加粗用删除线删除掉文字引用用>加空格就是引用格式分割线用---来表示分割线用***也可以表示分割线图片嵌入本地图片嵌入网络上的图片超链接......