线段树 整理笔记

p.s. 代码块缩进有问题,但不影响正常阅读,请忽略。

区间内最大连续权值和

  • P4513 小白逛公园

    区间询问 + 单点修改。

    对于询问区间内最大连续权值和,按照套路,维护 t i . l m a x t_i.lmax ti.lmax t i . r m a x t_i.rmax ti.rmax,注意在查询的时合并答案要分情况。具体地,看代码吧(实在描述不来):

    node query (int i, int l, int r){
       if (tree[i].l >= l and tree[i].r <= r) return tree[i];
       int f1 = 0, f2 = 0; node L, R;
       int mid = (tree[i].l + tree[i].r) >> 1;
       if (l <= mid) L = query (i * 2, l, r),f1 = 1;
       if (r > mid) R = query (i * 2 + 1, l, r), f2 = 1;
       if (f2 and f1){
           node m;
           m.maxl = max (L.maxl, L.sum + R.maxl);
           m.maxr = max (R.maxr, L.maxr + R.sum);
           m.ans = max (L.ans, max (R.ans, L.maxr + R.maxl));
           return m;
       }
       if (f1 == 1) return L; if (f2 == 1) return R;
    }
    

区间乘 + 区间加 + 区间查询

  • P2023 [AHOI2009] 维护序列

    模板。线段树乘法不过是多加了一个乘法懒标记,维护的时候复杂了些。

    inline void push_down(ll i){
       tree[i*2].sum=(ll)(tree[i].mul*tree[i*2].sum+((tree[i*2].r-tree[i*2].l+1)*tree[i].add)%mod)%mod;
       tree[i*2+1].sum=(ll)(tree[i].mul*tree[i*2+1].sum+((tree[i*2+1].r-tree[i*2+1].l+1)*tree[i].add)%mod)%mod;
       tree[i*2].mul=(ll)(tree[i*2].mul*tree[i].mul)%mod;
       tree[i*2+1].mul=(ll)(tree[i*2+1].mul*tree[i].mul)%mod;
       tree[i*2].add=(ll)(tree[i*2].add*tree[i].mul+tree[i].add)%mod;
       tree[i*2+1].add=(ll)(tree[i*2+1].add*tree[i].mul+tree[i].add)%mod;
       tree[i].mul=1;tree[i].add=0;
    }
    inline void add(ll i,ll l,ll r,ll k){
       if(tree[i].l>=l&&tree[i].r<=r){
           tree[i].add=(ll)(tree[i].add+k)%mod;
           tree[i].sum=(ll)(tree[i].sum+k*(tree[i].r-tree[i].l+1))%mod;
           return ;
       }push_down(i);
       ......(后续皆为基础操作)
    }
    inline void mult(ll i,ll l,ll r,ll k){
       if(tree[i].l>=l&&tree[i].r<=r){
           tree[i].mul=(tree[i].mul*k)%mod;
           tree[i].add=(tree[i].add*k)%mod;
           tree[i].sum=(tree[i].sum*k)%mod;
           return ;
       }push_down(i);
        ......(后续皆为基础操作)
    }
    

区间除法

  • [雅礼集训 2017 Day1]市场

    两个数在同时除以相同的大于 1 的数大约 l o g log log 次后,它们的差会小于等于 1。这就为我们将区间除转化为区间减做了保障。代码实现:

    inline void push_down (int i){
       if (t[i].tag){
           int lenl, lenr;
           lenl = t[i << 1].r - t[i << 1].l + 1;
           lenr = t[i << 1 | 1].r - t[i << 1 | 1].l + 1;//len
           t[i << 1].tag += t[i].tag;
           t[i << 1 | 1].tag += t[i].tag;
           t[i << 1].mx += t[i].tag;
           t[i << 1 | 1].mx += t[i].tag;
           t[i << 1].mn += t[i].tag;
           t[i << 1 | 1].mn += t[i].tag;
           t[i << 1].sum += 1ll * t[i].tag * lenl;
           t[i << 1 | 1].sum += 1ll * t[i].tag * lenr;
       }t[i].tag = 0;
       return;
    }
    inline void update_chu (int i, int l, int r, int k){
       if (t[i].l >= l and t[i].r <= r and t[i].mx - floor ((double)t[i].mx / k) == t[i].mn - floor ((double)t[i].mn / k)){
           int d = floor ((double)t[i].mx / k) - t[i].mx;
           t[i].tag += d;
           t[i].mn += d;
           t[i].mx += d;
           t[i].sum += d * (t[i].r - t[i].l + 1);
           return;
       }push_down (i);
       if (t[i << 1].r >= l) update_chu (i << 1, l, r, k);
       if (t[i << 1 | 1].l <= r) update_chu (i << 1 | 1, l, r, k);
       t[i].sum = t[i << 1].sum + t[i << 1 | 1].sum;
       t[i].mn = min (t[i << 1].mn, t[i << 1 | 1].mn);
       t[i].mx = max (t[i << 1].mx, t[i << 1 | 1].mx);
    }
    

区间排序

  • CF558E A Simple Task

    套路题。26 个字母较少,所以对于每个字母,开一个线段树,标记每一个字母出现的位置,若出现了该字母则该位置记为 1,否则记为 0。以升序为例。从 a 开始遍历 26 个字母,对于每个字母(此处以字母 a 为例),先统计它在该区间 [ l , r ] [l,r] [l,r] 内的出现次数 x x x。然后再进行区间修改操作——在这个字母的线段树内的 [ l , r ] [l,r] [l,r] 全部修改为 0,再将此线段树内的 [ l , l + x − 1 ] [l,l+x-1] [l,l+x1] 全部修改为 1。降序同理。最后再遍历所有线段树然后输出即可。附 code.


维护区间最小值次数 + 区间修改

  • P4898 [IOI2018] seats 排座位

    1

    如何判断节点集合 P P P 中的所有节点所组成的图形是否是一矩形?此处采用了一个染色的办法。假设我们现在将 P P P 中的节点全部染成黑色,将其余的节点全部染成白色。然后统计符合一下两个条件之一的点的总数:

    • 一个黑点,且它的左方和上方相邻的节点都是白色(或者为空)【条件一】;
    • 一个白点,且它的上下左右相邻的节点中有 ≥ 2 \geq2 2 个黑色的节点【条件二】。

    回过头来,看一个矩形中按上述方式能统计多少个点出来(顺便口胡证明):

    • 若一个黑点的左方和上方相邻的节点都是白色(或为空),则代表有一个连通块。显然,一个矩形中只有一个大连通块,若出现两个这样的黑色节点,那它肯定不是一个矩形。
    • 若有一个白点符合第二个条件,通过简单构造可以发现,不论黑色节点以什么方式出现在白点周围都无法在此基础上构造一个矩形。反过来说,当出现一个“L”形拐角时,拐角处的白点就符合第二个条件。综述,一个矩形不存在满足条件二的节点。

    所以,一个矩形的两者之和必定为 1。

    2

    有了 1 中的结论与判定方法,我们再看看和这道题有什么关联。

    发现我们可以将题目所求转化为:变量 i i i 从 1 遍历到 h ∗ w h * w hw,而 i i i 可以理解为一个时间点,即第 i i i 个时刻。在每个时刻 i i i,我们会在之前的基础上将第 i i i 为参赛者的座位染成黑色。即在时刻 i i i,第 1 到 i i i 位参赛者的座位都会被染上黑色。同时,记 v a l i val_i vali 表示时刻 i i i 时满足 1 中所述两个条件的总点数。

    由此可以得到一个重要性质:白点染色时间必定大于黑点染色时间。

    此时结合 1,就可以统计每个 v a l i val_i vali 的值:( r ( i ) r(i) r(i) 表示第 i i i 位参赛者的座位所在的行数; c ( i ) c(i) c(i) 表示所在列数; i i i 既代表时刻,也代表这个时刻要染色的节点; n u m x , y num_{x,y} numx,y 表示座位 ( x , y ) (x,y) (x,y) 的参赛者的编号。)

        #define rep(i, a, b) for(register int i = a; i <= b; ++i)
        rep(i, 1, s){ val[i] = val[i - 1];
            if(black(r(i), c(i)) > i) val[i] += 1;
            if(white(r(i), c(i)) < i) val[i] -= 1;
            rep(j, 0, 3) if(legal((xx = r(i) + fx[j][0]), (yy = c(i) + fx[j][1]))){
                if(black(xx, yy) == i and num(xx, yy) < i) val[i] -= 1;
                if(white(xx, yy) == i and num(xx, yy) > i) val[i] += 1;
            }
        }
    

    一点点来说,首先 b l a c k ( ) black() black() 函数是返回 i i i 节点左方和上方相邻节点它们染色时间的最小值。若这个最小值都大于 i i i,那么代表, i i i 这个节点是符合 1 中的条件一的节点,所以 v a l i val_i vali 加一。

    反之, w h i t e ( ) white() white() 函数是返回 i i i 节点四方相邻节点它们染色时间的次(第二)小值。若这个次小值小于 i i i,那么代表, i i i 这个节点此前是一个符合条件二的白点,但是现在它染成黑色,也就不满足条件二了,所以 v a l i val_i vali 减一。

    后面循环它的四方节点目的就是处理它是白点时对周围节点的影响。比如说,它曾经是其下方节点的 b l a c k ( ) black() black() 值,当时刻小于 i i i 且大于等于 n u m ( 下方节点坐标 ) num(\text{下方节点坐标}) num(下方节点坐标),下方节点就是满足条件一的黑点。但是当节点 i i i 染成黑色之后,它( i i i 节点的下方节点)就不再是满足条件一的节点了,所以 v a l i val_i vali 要减一。周围其他方向的节点同理。

    3

    然后再来谈谈交换座位。

    这其中也涉及到 1 中为什么要以那样的条件去检测所围图形是否为矩形——因为这样的判定方式不仅快和方便,而且交换时也简便容易。

    很简单,只需要把将要交换的两个节点及它们周围四联通的节点(即它们会影响到的节点)都存起来,并取消它们对 v a l val val 的影响,然后再交换那两个节点,再统计存起来的所有节点对 v a l val val 的影响即可。

    4

    综上,考虑我们要进行的操作。显然,我们要实时统计 v a l val val 中有多少个 v a l i val_i vali 的值为 1,并且在进行交换座位操作时,还要对 v a l val val 中某一区间的值进行修改。

    一言以蔽之,即全局最小值个数 + 区间修改。

    那不就是线段树嘛。

    再提一下为什么可以将“统计 v a l val val 中有多少个 v a l i val_i vali 的值为 1”转换为“全局最小值个数”。因为当只有第一个节点被染成黑色时,它必定是一个矩形,即 v a l 1 = 1 val_1 = 1 val1=1。所以直接统计全局最小值个数即可。

    注意将下标全部加一之后再统计操作。

    5 code.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值