线段树总结分析第三版(下)

简介: 线段树总结分析第三版

习题1最大连续区间维护,例题3的简化版:

题链:

Hotel

22ACM集训队-树状数组与线段树基础 - Virtual Judge (vjudge.net)

代码

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 5e4 + 10;
struct node{
  int l,    r;
  int fmax,lmax,rmax;
  int lazy;
}tr[N<<2];
void pushdown(node &tr,int lazy){
  tr.fmax = lazy * (tr.r - tr.l + 1);
  tr.lmax = tr.rmax = tr.fmax;
  tr.lazy = lazy;
}
void pushdown(int u){
  if(tr[u].lazy != -1){
    pushdown(tr[u<<1],tr[u].lazy) ,pushdown(tr[u<<1|1],tr[u].lazy);
    tr[u].lazy = -1;
  }
}
void pushup(node &tr,node &l,node &r){
  tr.fmax = max(max(l.fmax,r.fmax),l.rmax + r.lmax);
  tr.lmax = l.lmax + (l.lmax == l.r - l.l + 1 ? r.lmax : 0);
  tr.rmax = r.rmax + (r.rmax == r.r - r.l + 1 ? l.rmax : 0);
}
void pushup(int u){
  pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}
void build(int u,int l,int r){
  tr[u].l = l,tr[u].r = r,tr[u].fmax  = tr[u].lmax = tr[u].rmax = 1,tr[u].lazy = -1;
  if(l == r) return;
  int mid = l + r >> 1;
  build(u<<1,l,mid),build(u<<1|1,mid+1,r);
  pushup(u);
}
int findleft(int u,int v){
  if(tr[u].l == tr[u].r) return tr[u].l;
  pushdown(u);
  if(tr[u<<1].fmax >= v) return findleft(u<<1,v);
  if(tr[u<<1].rmax + tr[u<<1|1].lmax >= v) return tr[u<<1].r - tr[u<<1].rmax + 1;
  return findleft(u<<1|1,v);
}
void modify(int u,int l,int r,int v){
  if(tr[u].l >= l && tr[u].r <= r){
    pushdown(tr[u],v);
    return;
  }
  pushdown(u);
  int mid = tr[u].l + tr[u].r >> 1;
  if(l <= mid) modify(u<<1,l,r,v);
  if(r > mid) modify(u<<1|1,l,r,v);
  pushup(u);
}
int main(){
  int n,m;
  cin >> n >> m;
  build(1,1,n);
  while(m--){
    int op;
    scanf("%d",&op);
    if(op == 1){
      int x;
      scanf("%d",&x);
      if(tr[1].fmax >= x) {
        int l = findleft(1,x);
        printf("%d\n",l);
        modify(1,l,l+x-1,0);
      }
      else {
        printf("0\n");
      }
    }
    else{
      int l,r;
      scanf("%d%d",&l,&r);
      modify(1,l,l+r-1,1);
    }
  }
  return 0;
}

习题2批量替换,例题1的变式

就只是根据操作变了个pushdown。

根节点tr[1].sum,查询树的总和

代码

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
struct node{
  int l,r;
  int sum;
  int lazy;
}tr[N<<2];
void pushdown(node &tr,int lazy){
  tr.sum = lazy * (tr.r - tr.l + 1);
  tr.lazy = lazy;
}
void pushdown(int u){
  if(tr[u].lazy != -1){
    pushdown(tr[u<<1],tr[u].lazy),pushdown(tr[u<<1|1],tr[u].lazy);
    tr[u].lazy = -1;
  }
}
void pushup(int u){
  tr[u].sum = tr[u<<1].sum + tr[u<<1|1].sum;
}
void build(int u,int l,int r){
  tr[u] = {l,r,1,-1};
  if(l == r) return;
  int mid = l + r >> 1;
  build(u<<1,l,mid),build(u<<1|1,mid+1,r);
  pushup(u);
}
void modify(int u,int l,int r,int v){
  if(tr[u].l >= l && tr[u].r <= r){
    pushdown(tr[u],v);
    return;
  }
  pushdown(u);
  int mid = tr[u].l + tr[u].r >> 1;
  if(l <= mid) modify(u<<1,l,r,v);
  if(r > mid) modify(u<<1|1,l,r,v);
  pushup(u);
}
int main(){
  int t;
  cin >> t;
  for(int Case = 1;Case <= t;Case++){
    int n,m;
    scanf("%d%d",&n,&m);
    build(1,1,n);
    while(m--){
      int l,r,v;
      scanf("%d%d%d",&l,&r,&v);
      modify(1,l,r,v);
    }
    printf("Case %d: The total value of the hook is %d.\n",Case,tr[1].sum);
  }
  return 0;
}

2.批量自适应修改

题链

Q - Hotel

22ACM集训队-树状数组与线段树基础 - Virtual Judge (vjudge.net)

卡壳点:

忘记build,递归的 tr[u].l == tr[u].r == 写成 =

区间修改,题目给的时l和len,以为是l和r导致错误

用l和len表示r错误,写成了l + len,应该是l + len -1 ;

代码

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 5e4 + 10;
struct node{
  int l,    r;
  int fmax,lmax,rmax;
  int lazy;
}tr[N<<2];
void pushdown(node &tr,int lazy){
  tr.fmax = lazy * (tr.r - tr.l + 1);
  tr.lmax = tr.rmax = tr.fmax;
  tr.lazy = lazy;
}
void pushdown(int u){
  if(tr[u].lazy != -1){
    pushdown(tr[u<<1],tr[u].lazy) ,pushdown(tr[u<<1|1],tr[u].lazy);
    tr[u].lazy = -1;
  }
}
void pushup(node &tr,node &l,node &r){
  tr.fmax = max(max(l.fmax,r.fmax),l.rmax + r.lmax);
  tr.lmax = l.lmax + (l.lmax == l.r - l.l + 1 ? r.lmax : 0);
  tr.rmax = r.rmax + (r.rmax == r.r - r.l + 1 ? l.rmax : 0);
}
void pushup(int u){
  pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}
void build(int u,int l,int r){
  tr[u].l = l,tr[u].r = r,tr[u].fmax  = tr[u].lmax = tr[u].rmax = 1,tr[u].lazy = -1;
  if(l == r) return;
  int mid = l + r >> 1;
  build(u<<1,l,mid),build(u<<1|1,mid+1,r);
  pushup(u);
}
int findleft(int u,int v){
  if(tr[u].l == tr[u].r) return tr[u].l;
  pushdown(u);
  if(tr[u<<1].fmax >= v) return findleft(u<<1,v);
  if(tr[u<<1].rmax + tr[u<<1|1].lmax >= v) return tr[u<<1].r - tr[u<<1].rmax + 1;
  return findleft(u<<1|1,v);
}
void modify(int u,int l,int r,int v){
  if(tr[u].l >= l && tr[u].r <= r){
    pushdown(tr[u],v);
    return;
  }
  pushdown(u);
  int mid = tr[u].l + tr[u].r >> 1;
  if(l <= mid) modify(u<<1,l,r,v);
  if(r > mid) modify(u<<1|1,l,r,v);
  pushup(u);
}
int main(){
  int n,m;
  cin >> n >> m;
  build(1,1,n);
  while(m--){
    int op;
    scanf("%d",&op);
    if(op == 1){
      int x;
      scanf("%d",&x);
      if(tr[1].fmax >= x) {
        int l = findleft(1,x);
        printf("%d\n",l);
        modify(1,l,l+x-1,0);
      }
      else {
        printf("0\n");
      }
    }
    else{
      int l,r;
      scanf("%d%d",&l,&r);
      modify(1,l,l+r-1,1);
    }
  }
  return 0;
}

前提条件

是区间修改,区间查询,且修改操作的修改的值是根据具体节点储存的值而变化的,比如开根,幂,替换,乘除;

新做的题里发现替换可以直接批量赋值来实现,属于等值修改。

情景

对一个序列里的元素执行k次自适应操作,每次操作一个区间,然后询问区间内所有元素的值。

也有询问某个区间内所有值经过某种处理后的值。(此种问法是询问时用一个变量储存找到的值,经过处理后返回

例题1单种操作

Can you answer these queries?

22ACM集训队-树状数组与线段树基础 - Virtual Judge (vjudge.net)

主要就是把modify的递归条件改成了和传统query操作相同的有交集

复杂度比较高,需要一些剪枝

代码

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
struct node{
  int l,r;
  ll sum;
}tr[N<<2];
ll w[N];
void pushup(int u){
  tr[u].sum = tr[u<<1].sum + tr[u<<1|1].sum;
}
void build(int u,int l,int r){
  tr[u] = {l,r,w[r]};
//  cout << w[r] << endl;
  if(l == r) return;
  int mid = l + r >> 1;
  build(u<<1,l,mid),build(u<<1|1,mid+1,r);
  pushup(u);
}
ll query(int u,int l,int r){
  if(tr[u].l >= l && tr[u].r <= r) {
    return tr[u].sum;
  } 
//    cout << tr[u].sum;
  ll res =0 ;
  int mid = tr[u].l + tr[u].r >> 1;
  if(l <= mid) res += query(u<<1,l,r);
  if(r > mid) res += query(u<<1|1,l,r);
  return res;
}
void modify(int u,int l,int r){
  if(tr[u].l == tr[u].r) tr[u].sum = sqrt(tr[u].sum);
  else{
    if(tr[u].sum == tr[u].r - tr[u].l + 1) return;
    int mid = tr[u].l + tr[u].r >> 1;
    if(l <= mid) modify(u<<1,l,r);
    if(r > mid) modify(u<<1|1,l,r);
    pushup(u);  
  }
}
int main()
{
    int T = 1;
    int n, m;
    while (cin >> n) {
        for(int i = 1;i <= n;i++) scanf("%lld", &w[i]);
        build(1,1, n);
        printf("Case #%d:\n", T++);
        scanf("%d", &m);
        while (m--) {
            int op, l, r; scanf("%d %d %d", &op, &l, &r);
            if (l > r) swap(l, r);
//            cout << l << " " << r << endl;
            if (op) printf("%lld\n", query(1,l, r));
            else modify(1,l, r);
        }
        printf("\n");
    }
    return 0;
}

例题2多种操作

Transformation HDU - 4578

22ACM集训队-树状数组与线段树基础 - Virtual Judge (vjudge.net)

题解代码

Transformation HDU - 4578 (线段树,审题很重要)_Soar-的博客-CSDN博客

#include<bits/stdc++.h>
using namespace std;
#define lson i<<1,l,m
#define rson i<<1|1, m+1,r
const int mod = 10007;
const int maxn=1e5+10;
int x[maxn<<2],flag[maxn<<2];
 x是tr,flag是lazy
void pushup(int i,int l,int r)
{
    if(!flag[i<<1] || !flag[i<<1|1])左右子节点无值
        flag[i] = 0;
    else if(x[i<<1] != x[i<<1|1])左右子节点有值且不等
        flag[i] = 0;
    else flag[i]=1,x[i]=x[i<<1];左右子节点值相等
    所以这是一个记录懒标记的函数,如果左右子节点的值相同,就上传。
    通过用父节点的节点的值来代表子节点的值接受处理,降低复杂度
}
void pushdown(int i,int l,int r)
{
    if(flag[i])
    {
        flag[i<<1] = flag[i<<1|1] =1;
        x[i<<1] = x[i<<1|1] = x[i];
        flag[i]=0;
    }
    这是一个下传懒标记并处理懒标记的函数,如果有懒标记,说明这个节点是代表子节点接受处理的,所以直接将值下传到子节点,然后清除懒标记
}
void update(int ql,int qr,int p,int v,int i,int l,int r)
{
  妙:直接传入op,也就是p,根据p的值进行不同操作,减少了很多赘余的代码。
  我写时想的是写3个modify,也就是update,根据op不同,调用不同的modify,麻烦得很。
    if(ql<=l && qr>=r && flag[i])
    这里是有懒标记,且节点区间全都在需要处理的区间内,直接处理当前节点,然后pushdown,就可以实现区间处理
    {
        if(p==1)
            x[i] = (x[i]+v)%mod;
        else if(p==2)
            x[i] = (x[i]*v)%mod;
        else x[i] = v;
        修改当前节点值的话是不需要pushup的,因为pushup的操作是根据子节点的值来决定是否赋予当前节点一个懒标记,只修改当前节点值,代表当前节点已经是叶子节点,或者左右节点值相同,所以就算pushup了,懒标记还是会保持原有状态
        return;
    }
    pushdown(i,l,r);
 可能没有懒标记,会需要逐个单点修改,所以用两个if的原始query递归形式
    int m = (l+r)>>1;
    if(ql<=m) update(ql,qr,p,v,lson);
    if(qr>m) update(ql,qr,p,v,rson);
    进行子节点单点值修改操作后都需要pushup,来更新懒标记状态
    pushup(i,l,r);
}
int query(int ql,int qr,int num,int i,int l,int r)
l,r是当前节点的l,r
{
    if(flag[i] && ql<=l && qr>=r)
    {
        int ans=1;
        for(int j=0;j<num;j++)ans=(ans*x[i])%mod;//pow操作,每次pow取余,如果是10007的三次方就有可能爆int了,所以用循环来每次操作后取余
        ans=(ans*(r-l+1))%mod;
        return ans;
    }
    pushdown(i,l,r);
    int m = (l+r)>>1;
    int ans=0;
    if(ql<=m)ans+=query(ql,qr,num,lson);
    if(qr>m)ans+=query(ql,qr,num,rson);
    return ans%mod;
}
int main()
{
    int n,m;
    while(cin>>n>>m,n||m)
    {
        memset(flag,1,sizeof flag);
        memset(x,0,sizeof x);
        int p,x,y,v;
        while(m--)
        {
            scanf("%d%d%d%d",&p,&x,&y,&v);
            if(p>=1 && p<=3)update(x,y,p,v,1,1,n);
            else printf("%d\n",query(x,y,v,1,1,n));
        }
    }
}

经过模仿后得到的acwing版代码

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10,mod = 10007;
struct node{
  int l,r;
  int sum;
  int lazy;
}tr[N<<2];
void pushup(int u){
  if(!tr[u<<1].lazy || !tr[u<<1|1].lazy) tr[u].lazy = 0;
  有一个子节点懒标记是0(当前节点的子节点的两个子节点的值不相等)则当前节点懒标记就变成0,由此可以推断出,懒标记的含义是表示当前节点的子树里所有节点的值 ,都相等,可以直接用当前节点的值来进行操作。 
  else if(tr[u<<1].sum != tr[u<<1|1].sum) tr[u].lazy = 0;
  else tr[u].lazy = 1,tr[u].sum = tr[u<<1].sum;
}
void pushdown(int u){
  if(tr[u].lazy){
    tr[u<<1].lazy = tr[u<<1|1].lazy = 1;
    tr[u<<1].sum = tr[u<<1|1].sum = tr[u].sum;
    tr[u].lazy = 0;
  }
}
void build(int u,int l,int r){
只有叶子节点才能初始化成lazy1
  if(l == r){
    tr[u] = {l,r,0,1};
     return;
  }
  tr[u] = {l,r};
  int mid = l + r >> 1;
  build(u<<1,l,mid),build(u<<1|1,mid+1,r);
}
void modify(int u,int l,int r,int op,int v){
  if(tr[u].l >= l && tr[u].r <= r && tr[u].lazy){
    if(op == 1) tr[u].sum = (tr[u].sum + v) % mod;
    else if(op == 2) tr[u].sum = (tr[u].sum * v)%mod;
    else {
      tr[u].sum = v;  
    }   
    return;
  }
  pushdown(u);
  有懒标记要先处理,然后再运算。
  int mid = tr[u].l + tr[u].r >> 1;
  if(l <= mid) modify(u<<1,l,r,op,v);
  if(r > mid) modify(u<<1|1,l,r,op,v);
  pushup(u);
}
int query(int u,int l,int r,int v){
  if(tr[u].l  >= l && tr[u].r <= r && tr[u].lazy){
    int res = 1;
    for(int i = 0;i < v;i++) res = (res * tr[u].sum) % mod;
    res = res * (tr[u].r - tr[u].l + 1) % mod;
    return res;
  }
  pushdown(u);
  int mid = tr[u].l + tr[u].r >> 1;
  int res = 0;
  if(l <= mid) res = (res + query(u<<1,l,r,v) ) %mod; 
  if(r > mid) res = (res + query(u<<1|1,l,r,v)) % mod;
  return res % mod;
}
int main(){
  int n,m;
  while(cin >> n >> m,n||m){
//    for(int i=0;i <= N << 2;i++) tr[i]= {0,0,0,0};  
    build(1,1,n);
    int op,l,r,v;
    while(m--){
      scanf("%d%d%d%d",&op,&l,&r,&v);
//      cout << op << " " << l << " " << r << " " << v << endl;
      if(op >=1 && op <= 3){
        modify(1,l,r,op,v);
      }
      else {
        printf("%d\n",query(1,l,r,v));
      }
    }
  }
  return  0;
}

注意

注意点就是非数组模拟节点的代码要用build初始化,然后叶子节点懒标记初始化为1,因为代表的含义是两个子节点值是否相等
懒标记区间自适应修改至少耗时2s,如果能操作简单,能用简单的语句来确定是否可以省略操作(如例题1tr[u].r - tr[u].l + 1) == tr[u].sum)判断区间内是否全为1而可以省略掉区间开方操作。

其他套路:

涉及到一个多组输入的套路

前提条件是没有明确组数,结束关键词的多组数据集输入

取反while(~scanf(“%d”,&n)

和while(scanf(“%d”,&n) != EOF)

还有while(cin >> n)三种形式

习题1开方单点剪枝1e5 + 2e5,常数n为6以内:

题链

花神游历各国

22ACM集训队-树状数组与线段树基础 - Virtual Judge (vjudge.net)

比较

与例题1是同一题型,但例题1的题解里没有用到fmax来维护区间最大值,而是直接用

tr[u].sum 与 tr[u].r - tr[u].l +1 是否相等来判断是否return。

例题1的做法仅限于元素最小是1的情况下,

习题1的元素最小为0,所以不能用这种做法。

于是归结出第一个剪枝套路

卡壳点:

没注意node内的maxn也要在modify内开方。

注意操作时除了l,r之外的属性基本都要处理

build初始化,注意叶子节点和根节点的初始化可能会不同。

代码

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
struct node{
  int l,r;
  ll sum;
  int maxn;
}tr[N<<2];
ll w[N];
void pushup(int u){
  tr[u].sum = tr[u<<1].sum + tr[u<<1|1].sum;
  tr[u].maxn = max(tr[u<<1].maxn,tr[u<<1|1].maxn);
}
void build(int u,int l,int r){
  if(l == r){
    tr[u] = {l,r,w[r],w[r]};
    return;
  }tr[u] = {l,r,0,0 };
//  cout << w[r] << endl;
  int mid = l + r >> 1;
  build(u<<1,l,mid),build(u<<1|1,mid+1,r);
  pushup(u);
}
ll query(int u,int l,int r){
  if(tr[u].l >= l && tr[u].r <= r) {
    return tr[u].sum;
  } 
//    cout << tr[u].sum;
  ll res =0 ;
  int mid = tr[u].l + tr[u].r >> 1;
  if(l <= mid) res += query(u<<1,l,r);
  if(r > mid) res += query(u<<1|1,l,r);
  return res;
}
void modify(int u,int l,int r){
      if(tr[u].maxn <= 1) return;
  if(tr[u].l == tr[u].r) {
  tr[u].sum = sqrt(tr[u].sum);       tr[u].maxn = sqrt(tr[u].maxn);return;}
    int mid = tr[u].l + tr[u].r >> 1;
    if(l <= mid) modify(u<<1,l,r);
    if(r > mid) modify(u<<1|1,l,r);
    pushup(u);  
}
int main()
{
    int T = 1;
    int n, m;
    while (cin >> n) {
        for(int i = 1;i <= n;i++) scanf("%lld", &w[i]);
        build(1,1, n);
//        printf("Case #%d:\n", T++);
        scanf("%d", &m);
        while (m--) {
            int op, l, r; scanf("%d %d %d", &op, &l, &r);
//            cout << l << " " << r << endl;
            if (op==1) printf("%lld\n", query(1,l, r));
            else modify(1,l, r);
        }
        printf("\n");
    }
    return 0;
}

套路:

区间单点修改开平方剪枝:

前提条件,

对区间进行开平方,并需要动态维护区间和:

应对

节点中用fmax维护区间最值,modify时区间最值<=1就直接return。

如果节点值最小是1的话,直接通过判断tr[u].sum 与 tr[u].r - tr[u].l +1 是否相等可以确定是否需要剪枝。(区间值全为1)

3.区间染色

普通例题

题链

22ACM集训队-树状数组与线段树基础 - Virtual Judge (vjudge.net)

套路:

lazy代替sum:

前提条件:

题目不需要求区间长度,操作时各种值无优先级,在区间上互相进行区间覆盖操作,求最后的状态。

情景

操作之间存在覆盖,遮挡的影响

之前画的一些线段可能会被后面的一些线段所覆盖
给出每张海报所贴的范围li,ri(1<=li<=ri<=10000000) 。求出最后还能看见多少张海报。
疑问

染过色后lazy被pushdown下移会不会有影响?

:modify经过lazy所在节点时,也就是lazy底下有节点被另外染色了此时lazy下移,这个节点没资格再代表其下的所有节点了。

判断是否颜色相同可以用个pushup,见批量自适应修改里的例题2多种操作里的pushup,但没什么必要。

代码

统计颜色种类及段数。

卡壳点:segment fault

用N作为下标访问元素时要养成写N-1的习惯,防止出现数组越界

#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 1E4 + 10;
cou要开8011
int cou[N];
struct node {
    int l, r;
    int lazy;
}t[N << 2];
void pushdown(node& op, int lazy) { op.lazy = lazy; }
void pushdown(int x) {
    if (!t[x].lazy) return; 
    pushdown(t[x << 1], t[x].lazy), pushdown(t[x << 1 | 1], t[x].lazy);
    t[x].lazy = 0;
}
void build(int l, int r, int x = 1) {
  t[x] = { l, r, 0 };
    if (l == r) return;
    int mid = l + r >> 1;
    build(l, mid, x << 1), build(mid + 1, r, x << 1 | 1);
}
void modify(int l, int r, int c, int x = 1) {
    if (l <= t[x].l && r >= t[x].r) { pushdown(t[x], c); return; }
    写错条件成叶子节点。
    pushdown(x);
    int mid = t[x].l + t[x].r >> 1;
    忘记/2
    if (l <= mid) modify(l, r, c, x << 1);
    if (r > mid) modify(l, r, c, x << 1 | 1);
    忘记变成x<<1|1
}
int last = 0; //表示上一次碰到的颜色, 记得当遍历叶子结点时没有颜色的时候也要记录.
void ask(int x = 1) {
    if (last != t[x].lazy) cou[t[x].lazy]++;
    判断是否间隔开
    if (t[x].lazy || t[x].l == t[x].r) { last = t[x].lazy; return; }
    当前节点lazy不为0,表示当前节点下的所有节点都经过区间修改,变成一样的颜色,不用再继续深入。
    //  有标记直接进上面的语句了,不需要pushdown 
    遍历整棵树
    ask(x << 1), ask(x << 1 | 1);
    先左后右保证last一定在t[x]的左边
}
int main()
{
    int n;
    while (~scanf("%d", &n)) {
        build(1, 8010); last = 0;  
        memset(cou, 0, sizeof cou);
        o1查询的优化。
        rep(i, n) {
            int l, r, c; scanf("%d %d %d", &l, &r, &c);
            modify(l + 1, r, c + 1); //这里是为了让建树区间下标从1开始, 涂色记录也从1开始
        }
        ask();
        rep(i, 8010) if (cou[i]) printf("%d %d\n", i - 1, cou[i]);
        printf("\n");
    }
    return 0;
}

离散化例题

离散化,下标很大,值很小

我们看这组数据[1,10],[1,3],[6,10],很明显答案是3

但是离散化之后为[1,4],[1,2],[3,4],答案变成了2

为解决这种问题,我们可以在更新线段树的时候将区间从[l,r]变成[l,r-1],就将区间转化成了[1,3],[1,1],[3,3]这样的树

但是当我们遇到这样的数据[1,3],[1,1],[2,2],[3,3],就会导致区间更新时出错,我们可以将初始数据的r都加上1,就排除了li和ri相等的情况,如果没有这种情况,离散化后的区间也都是一样的

其实这道题数据很弱,不管这样的情况也能过(逃

正解https://www.luogu.com.cn/paste/vl2ora2z

#include <iostream>
#include <vector>
#include <cstdio>
#include <algorithm>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 1E5 + 10;
bool vis[N]; //标记是否已经出现过第i张海报
struct node {
   int l, r;
   int id; //id表示lazy标签, 也表示当前节点值. 
}t[N << 2];
vector<int> v; vector<pair<int, int> > area; //v为离散化数组, area存放海报区间
int find(int x) { return lower_bound(v.begin(), v.end(), x) - v.begin(); }
离散化用
void pushdown(node& op, int id) { op.id = id; }
void pushdown(int x) {
   if (!t[x].id) return;
   pushdown(t[x << 1], t[x].id), pushdown(t[x << 1 | 1], t[x].id);
   t[x].id = 0; 
}
//因为线段树内部不维护任何数值, 所以也可以省去pushup这一操作.
void build(int l, int r, int x = 1) {
   t[x].l = l,t[x].r = r,t[x].id = 0 ;//id: 特别的, 如果0也是染色的点, 那么应初始化为-1
   if (l == r) return;
   int mid = l + r >> 1;
   build(l, mid, x << 1), build(mid + 1, r, x << 1 | 1);
}
void modify(int l, int r, int c, int x = 1) {
   if (l <= t[x].l && r >= t[x].r) { pushdown(t[x], c); return; }
   pushdown(x);
   int mid = t[x].l + t[x].r >> 1;
   if (l <= mid) modify(l, r, c, x << 1);
   if (r > mid) modify(l, r, c, x << 1 | 1);
}
int ask(int x = 1) { 
   if (t[x].id) { //当前子树均为同一值, 没必要再递归下去了
       if (vis[t[x].id]) return 0;
       return vis[t[x].id] = 1;
   }
   if (t[x].l == t[x].r) return 0; //到叶子结点一定要结束递归
   return ask(x << 1) + ask(x << 1 | 1);
}
int main()
{
   int T; cin >> T; 
   while (T--) {
       v.clear(); v.push_back(-0x3f3f3f3f); //这里是为了离散化下标从1开始
       area.clear();
       int n; scanf("%d", &n);
       rep(i, n) {
           vis[i] = 0; //顺带初始化vis
           int l, r; scanf("%d %d", &l, &r);
           r++; 
           v.push_back(l), v.push_back(r);
           area.push_back(make_pair(l,r));
       }
       /* 离散化部分 */
       sort(v.begin(), v.end()); v.erase(unique(v.begin(), v.end()), v.end());
       rep(i, n) if (v[i] - v[i - 1] != 1) v.push_back(v[i] - 1); //记为*
       sort(v.begin(), v.end());
       build(1, v.size() - 1); //因为我的v数组有个-INF, 所以实际的建树大小应为v.size()-1
       for (int i = 0; i < n; ++i) {
           int l = area[i].first, r = area[i].second;
           l = find(l), r = find(r);
           modify(l, r-1, i + 1); //个人习惯编号从1开始
       }
       printf("%d\n", ask());
   }
   return 0;
}

区别

区间等值修改里,

lazy存的是子树中所有节点要修改的值或要修改成的值,根据题意决定初始化的值,如例题1中批量+v操作,初始化为0,例题2批量替换,初始化为v的定义域之外的值。

sum存的是子树所有节点sum的总和 ,一般初始化为0.

是一个修改时给节点打上lazy,遇到lazy时解开lazy的过程。


区间自适应修改里,

lazy作为一个bool变量,存的是当前节点是否能代表子树接受修改,根据题意决定初始化的值

sum只有在lazy为1时有实际意义,

存的是这颗所有节点的值都相同的子树里的这个相同的值

修改值时,直接修改节点的值


目录
相关文章
|
6月前
|
存储 算法 C++
【AcWing算法基础课】第二章 数据结构(部分待更)(3)
路径压缩:查找时,如果还没有找到目标值的父结点时,将路径上每个点的父结点,在向上寻找过程中更新记录。
58 0
|
6月前
|
存储 算法
【AcWing算法基础课】第二章 数据结构(部分待更)(2)
给定一个长度为 N 的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 −1。
51 0
|
6月前
|
存储 算法
【AcWing算法基础课】第二章 数据结构(部分待更)(1)
e数组存储每个结点的值,ne数组存储每个结点的指向的下一个结点。
56 0
|
9月前
|
算法
算法导论(第三版)具体算法解析与理解
算法导论(第三版)具体算法解析与理解
|
11月前
|
存储 算法 Java
Java数据结构与算法分析(十)B树图文详解(含完整代码)
迄今为止,已经介绍了《 二叉查找树 》和《 AVL树 》,我们始终假设可以把整个数据结构存储在内存中。可是,如果数据多到内存装不下,这就意味着必须把数据放在磁盘上,显然这些数据结构不再适用。 问题在于磁盘的I/O速度是远远不如内存访问速度的,然而从一棵树中查找到某个元素,必须从根节点一层层往下找,这每一次查找便是一次I/O操作。为了提高性能,就必须要减少查找的次数。 如能减少树的高度、增加每个节点中的元素数,便是种有效的解决方案。实现这种想法的一种方法是使用B树。
81 0
|
机器学习/深度学习
线段树与树状数组总结分析(可能是最终版)(下)
线段树与树状数组总结分析(可能是最终版)
61 0
线段树与树状数组总结分析(可能是最终版)(中)
线段树与树状数组总结分析(可能是最终版)
33 0
线段树与树状数组总结分析(可能是最终版)(上)
线段树与树状数组总结分析(可能是最终版)
37 0
|
机器学习/深度学习 人工智能 C语言
数据结构(荣誉)实验五 树状数组
数据结构(荣誉)实验五 树状数组
86 0