CodeForces - 915E 动态开点线段树

题目链接

大意:

晚上有n个亮着的灯泡,标号从1到n。

现在存在2种操作,如下:

  • 操作1,关掉标号 [l,r] 区间的灯
  • 操作2,打开标号 [l,r] 区间的灯

下面有q次询问,每次询问执行其中一种操作,询问格式,l,r,k。k为执行操作种类。对于每次询问回答当前开着的灯的数量。

第一行包含一个整数n,第二行一个整数q(1≤n≤10^9,1≤q≤3·10^5) 

因为n范围过大,如果用静态开点需要离散化,但是离散化之后就无法根据离散化后的点得到区间长度 ,还需要再开数据记录,有点麻烦,所以这题我们用动态开点,不需要的点不开辟空间就好。

动态开点需要用l,r数组记录当前点的左右子节点,而不能再用rt<<1和rt<<1|1来表示了。

用数组记录左右子节点,如果左右子节点还没有出现过,给它赋值为cnt++(cnt用于给所有出现的节点编号,这样出现了多少点,就开辟了多少空间)

这题是区间更新,代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn=3e5+10;
struct vain
{
    int l,r,sum,lazy;
} tree[maxn*50];
int cnt=1;
void pushdown(int l,int r,int k)
{
    int mid=(l+r)>>1;
    if (l!=r)
    {
        if (!tree[k].l)
            tree[k].l=++cnt;
        if (!tree[k].r)
            tree[k].r=++cnt;
        //下传判断儿子是否存在,不存在,便增加节点
        tree[tree[k].l].sum=(mid-l+1)*tree[k].lazy;
        tree[tree[k].r].sum=(r-mid)*tree[k].lazy;
        tree[tree[k].l].lazy=tree[k].lazy;
        tree[tree[k].r].lazy=tree[k].lazy;
    }
    tree[k].lazy=-1;
}

void update(int l,int r,int &k,int L,int R,int p)
{
    if (!k)
    {
        k=++cnt;//遇到开过的点就不开,没开过开新的节点
        tree[k].lazy=-1;
    }
    if (l>=L && r<=R)
    {
        tree[k].sum=p*(r-l+1);
        tree[k].lazy=p;
        return;
    }
    if (tree[k].lazy!=-1)
        pushdown(l,r,k);
    int mid=(l+r)>>1;
    if (mid>=L)
        update(l,mid,tree[k].l,L,R,p);
    if (mid<R)
        update(mid+1,r,tree[k].r,L,R,p);
    tree[k].sum=tree[tree[k].l].sum+tree[tree[k].r].sum;
}
int main()
{
    int n,q;
    scanf("%d %d",&n,&q);
    int k=1;
    for (int i=1; i<=q; i++)
    {
        int l,r,p;
        scanf("%d %d %d",&l,&r,&p);
        p=2-p;  //打开为0,关闭为1,因为初始化值为0,而所有的灯初始状态都是打开的
        update(1,n,k,l,r,p);
        printf("%d\n",n-tree[1].sum);
    }
}

单点更新:

不需要给左右子节点赋值,因为不需要下传

#include<bits/stdc++.h>
#define LOG 20
using namespace std;
const int maxn=100010;
struct node
{
    int l,r,sum;
}k[maxn*20];
int rt,ncnt,lc[maxn*LOG],rc[maxn*LOG],sum[maxn*LOG];
inline void pushup(int x)
{
    k[x].sum=k[k[x].l].sum+k[k[x].r].sum;//更新
}
inline void update(int &x,int l,int r,int m,int val)
{
    if(!x)
        x=++ncnt;//开点
    if(l==r)
    {
        k[x].sum+=val;
        return;
    }
    int mid=(l+r)>>1;
    if(m<=mid)
        update(k[x].l,l,mid,m,val);
    else
        update(k[x].r,mid+1,r,m,val);
    pushup(x);
}
int ask(int x,int l,int r,int L,int R)
{
    if(!x)
        return 0;//没这个点,直接返回0
    if(L<=l && R>=r)
        return k[x].sum;
    int val=0;
    int mid=(l+r)>>1;
    if(L<=mid)
        val+=ask(k[x].l,l,mid,L,R);
    if(R>mid)
        val+=ask(k[x].r,mid+1,r,L,R);//递归计算
    return val;
}
int main()
{
    int n;
    cin>>n;
    for(int i=1; i<=n; i++)
    {
        int num;
        cin>>num;
        update(rt,1,n,i,num);
    }
    int q;
    cin>>q;
    while(q--)
    {
        int l,r;
        cin>>l>>r;
        cout<<ask(rt,1,n,l,r)<<endl;
    }
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值