动态开点线段树好题(推荐)

[题目链接] (https://acm.xylab.fun/judge/problem/view/1028)
题意:对于一个1-n的序列,有两种操作,第一种更改一个位置的值第二种就是查询[l,r]里x出现奇数次的子区间的个数。

题解:我们对每个位置的数建一颗区间01线段树,那么我们动态开点,注意这里有多个根,也就是说有很多颗线段树。那么我们可以维护五个值:val,len,sumL,sumR,sum,分别是线段树节点合法区间个数,区间长度,包含左端点的合法区间个数,包含右端点的合法区间个数,区间和。最关键应该考虑的是区间的合并,val = ls[o].val + rs[o].val , val += sumR*(len-sumL),val += (len-sumR) * sumL ;那么sumL,sumR怎么合并,请读者思考。

#include
#define ll long long
using namespace std;
const int maxn = 4e5+10;
int a[maxn],cnz=0,cnt=0;
mapmp;
struct node {
    ll val;
    int len, sumL, sumR, sum;
    node operator+(const node &t) const {
        node tmp;
        tmp.val = val + t.val;
        tmp.val += 1ll * sumR * (t.len - t.sumL);
        tmp.val += 1ll * (len - sumR) * t.sumL;
        tmp.len = len + t.len;
        tmp.sum = sum + t.sum;
        if (sum % 2)
            tmp.sumL = sumL + t.len - t.sumL;
        else
            tmp.sumL = sumL + t.sumL;
        if (t.sum % 2)
            tmp.sumR = t.sumR + len - sumR;
        else
            tmp.sumR = t.sumR + sumR;
        return tmp;
    }
} tr[maxn * 24];
int ls[maxn*24],rs[maxn*24],rt[maxn*2];
void init(int& o,int len){
    o = ++cnt;
    tr[o].len = len;
}
void up(int& o,int l,int r,int k,int p)
{
    if(!o) o = ++cnt;
    if(l==r)
    {
        tr[o].val = tr[o].sumL = tr[o].sumR = tr[o].sum = p;
        tr[o].len = 1;
        return ;
    }
    int m = (l+r)/2;
    if(k<=m) up(ls[o],l,m,k,p);
    else up(rs[o],m+1,r,k,p);
    if(!ls[o])init(ls[o],m-l+1);
    if(!rs[o])init(rs[o],r-m);
    tr[o] = tr[ls[o]] + tr[rs[o]];
}
node qu(int& o,int l,int r,int ql,int qr)
{
    if(!o) init(o,r-l+1);
    if(ql<=l&&qr>=r)return tr[o];
    int m=(l+r)/2;
    if(ql>m) return qu(rs[o],m+1,r,ql,qr);
    else if(qr<=m) return qu(ls[o],l,m,ql,qr);
    else return qu(ls[o],l,m,ql,qr) + qu(rs[o],m+1,r,ql,qr);
}
int main()
{
    int n;scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        if(!mp[a[i]])
        mp[a[i]] = ++cnz;
        up(rt[mp[a[i]]],1,n,i,1);
    }
     int q;scanf("%d",&q);
      while(q--)
      {
          int op;scanf("%d",&op);
          if(op==1)
          {
              int p,v;
            scanf("%d%d",&p,&v);
            up(rt[mp[a[p]]],1,n,p,0);
            a[p] = v;
            if(!mp[a[p]])
            mp[a[p]] = ++cnz;
            up(rt[mp[a[p]]],1,n,p,1);
          }
          else {
            int l,r,x;
            scanf("%d%d%d",&l,&r,&x);
          printf("%lld\n",qu(rt[mp[x]],1,n,l,r).val);
          }
      }
}

如果理解动态开点 线段树的,应该很容易看懂,提醒一下,init()函数就是因为你合并区间长度的时候,有些点没开,所以要给一些点区间长度。

你可能感兴趣的:(数据结构--线段树,动态开点01线段树)