[NOIP校内集训]multiset(动态开点线段树/平衡树+线段树)

题意

有n个格子\((n\leq 1e5)\),初始每个格子的权值为0。支持两个操作:1.每次向\([l,r]\)中的所有格子加入一个物品i,对于一个格子,如果是第一次加入i,则权值加1,否则权值翻倍;2.区间求和
物品种类和n同阶

思路

考虑到虽然物品种类很多,但操作区间数量仍然是1e5级别,于是可以用一个动态的数据结构来维护

1.set+线段树

考场做法

第一种思路就是无脑的开和种类数一样多的set,每个set维护一个颜色的所有区间,于是问题就变成了快速的找到待加入区间与已经有的区间的重叠部分,将重叠部分乘2,其余部分加1即可。

类似会场预约,可以直接用find操作找到所有重叠区间进行修改操作,然后我们将它们删掉,和待加入区间融合,然后加入将它们作为一整块区间加入set。因为一个区间被遍历一次就会被删掉,所以整个算法的时间复杂度为\(O(nlog^2n)\)

Code

#include<bits/stdc++.h>
#define N 200005
#define Min(x,y) ((x)<(y)?(x):(y))
#define Max(x,y) ((x)>(y)?(x):(y))
#define IT set<SXK>::iterator
using namespace std;
typedef long long ll;
const ll mod = 998244353;
int n,q;
ll sum[N<<2],sign_mul[N<<2],sign_add[N<<2];
struct SXK
{
    int l,r;
    SXK(int ll=0,int rr=0) {l=ll;r=rr;}
    bool operator < (const SXK a)const
    {
        return r < a.l;
    }
};//保证同颜色的区间不重叠 
set<SXK> col[N];

template <class T>
void read(T &x)
{
    char c;int sign=1;
    while((c=getchar())>'9'||c<'0') if(c=='-') sign=-1; x=c-48;
    while((c=getchar())>='0'&&c<='9') x=x*10+c-48; x*=sign;
}

void add_sign_add(int rt,int l,int r,ll val)
{
    sign_add[rt]=(sign_add[rt]+val)%mod;
    sum[rt]=(sum[rt]+(r-l+1)*val%mod)%mod;
}
void add_sign_mul(int rt,int l,int r,ll val)
{
    sign_add[rt]=sign_add[rt]*val%mod;
    sign_mul[rt]=sign_mul[rt]*val%mod;
    sum[rt]=sum[rt]*val%mod;
}
void pushdown(int rt,int l,int r)
{
    if(sign_mul[rt]==1&&sign_add[rt]==0) return;
    int mid=(l+r)>>1;
    add_sign_mul(rt<<1,l,mid,sign_mul[rt]); 
    add_sign_add(rt<<1,l,mid,sign_add[rt]);
    add_sign_mul(rt<<1|1,mid+1,r,sign_mul[rt]);
    add_sign_add(rt<<1|1,mid+1,r,sign_add[rt]); 
    sign_mul[rt]=1;
    sign_add[rt]=0;
}
void modify_add(int rt,int l,int r,int x,int y)
{
    if(x>y) return;
    if(x<=l&&r<=y) return add_sign_add(rt,l,r,1);
    int mid=(l+r)>>1;
    pushdown(rt,l,r);
    if(x<=mid) modify_add(rt<<1,l,mid,x,y);
    if(y>mid) modify_add(rt<<1|1,mid+1,r,x,y);
    sum[rt]=(sum[rt<<1]+sum[rt<<1|1])%mod;
}
void modify_mul(int rt,int l,int r,int x,int y)
{
    if(x>y) return;
    if(x<=l&&r<=y) return add_sign_mul(rt,l,r,2);
    int mid=(l+r)>>1;
    pushdown(rt,l,r);
    if(x<=mid) modify_mul(rt<<1,l,mid,x,y);
    if(y>mid) modify_mul(rt<<1|1,mid+1,r,x,y);
    sum[rt]=(sum[rt<<1]+sum[rt<<1|1])%mod;
}
ll query(int rt,int l,int r,int x,int y)
{
    if(x<=l&&r<=y) return sum[rt];
    int mid=(l+r)>>1;
    pushdown(rt,l,r);
    ll ret=0;
    if(x<=mid) ret+=query(rt<<1,l,mid,x,y);
    if(y>mid) ret+=query(rt<<1|1,mid+1,r,x,y);
    return ret%mod;
}

int main()
{
    freopen("multiset.in","r",stdin);
    freopen("multiset.out","w",stdout);
    read(n);read(q);
    for(int i=0,t=N-1;i<=t;++i) sign_mul[i]=1;
    while(q--)
    {
        int opt,l,r,x;
        read(opt);read(l);read(r);
        if(opt==1)
        {
            read(x);//向[l,r]加入x
            int L=l,R=r;
            int dl=l;//dl表示现在应该处理的最左边 
            IT it=col[x].find(SXK(l,r));//找到第一个有交集的区间 
            while( it!=col[x].end() )
            {
                modify_add(1,1,n,dl,it->l - 1);
                modify_mul(1,1,n,Max(it->l,dl),Min(it->r,r));
                dl=it->r+1;
                L=Min(L,it->l);
                R=Max(R,it->r);
                col[x].erase(it);
                it=col[x].find(SXK(l,r));
            }
            modify_add(1,1,n,dl,r);//处理最后一段 
            ////这一段可以有更简单的写法,时间差不多
            col[x].insert(SXK(L,R));
        }
        else printf("%lld\n",query(1,1,n,l,r));
    }
    return 0;
}

2.动态开点线段树

显然,一个颜色一个线段树会爆炸的原因是开了许多无用空间,直接动态开点线段树优化即可(如此简单

猜你喜欢

转载自www.cnblogs.com/Chtholly/p/11574190.html