【分块】LGP5113 Sabbat of the witch

【题目】
原题地址
给定一个长度为 n n n的数列 a a a,要求支持三种操作:

  • 区间赋值为 x x x
  • 区间求和
  • 撤销某次赋值操作,即去掉那一次操作
    n , m ≤ 1 0 5 n,m\leq 10^5 n,m105,操作 1 1 1次数不超过 65000 65000 65000次,所有输入不超过 1 0 9 10^9 109,强制在线。

【解题思路】
一道十分暴力的分块题啊。
首先我们考虑一个比较 n a i v e naive naive O ( n n log ⁡ n ) O(n\sqrt n \log n) O(nn logn)做法:首先对序列进行分块。序列的每个位置维护一个 set \text{set} set记录覆盖了这个位置的所有修改,每个块再维护一个 set \text{set} set记录覆盖了整个块的所有修改。那么接下来询问操作我们也可以很方便地计算了。

具体来说:我们将块内中的每个点按照最后有效操作时间排序,那么一个块的和可以这样求:二分出来那些点的生效时间在块的生效时间之后,假设这些点的和为 a a a,然后还可以得到有几个点颜色被块上的修改所覆盖了,设这些点的个数为 b b b,当前块上数字为 k k k,那么块中元素的和就是 k b + a kb+a kb+a

由于每次修改操作我们只需要对两边散点的块重新排序,所以三种操作的复杂度都是 O ( n log ⁡ n ) O(\sqrt n \log n) O(n logn)的。

现在我们要将这个 log ⁡ \log log去掉,考虑分块的暴力性。
我们每个位置和块上维护的 set \text{set} set实际上是没有用的,因为插入的数字是单调的,所以我们只需要用一个栈来维护可以达到相同的效果。
排序方面,由于操作 1 1 1的次数不超过 m = 65000 m=65000 m=65000次,我们可以考虑使用基数排序,在 ⌈ m ⌉ \lceil \sqrt m \rceil m 进制下,我们可以用 m \sqrt m m 的时间进行排序。实际上我们取 m = 65536 m=65536 m=65536的话可以用 256 256 256进制简单处理。
最后二分,我们发现如果块的栈顶元素是在重构之后丢进去的话整个块将会是一个值,否则栈顶就是重构之前的最大值,而显然重构之前的最大值是单调不增的,维护一个指针每次暴力向左爬即可。

最后复杂度就是 O ( n n ) O(n\sqrt n) O(nn )的了,由于 n n n太大,甚至不能使用 STL \text{STL} STL

【参考代码】

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N=1e5+10,S=250,M=255;
int n,m,cnt;
ll ans;

namespace IO
{
    int read()
    {
        int ret=0;char c=getchar();
        while(!isdigit(c)) c=getchar();
        while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
        return ret;
    }
    void write(ll x)
    {
        if(x>9) write(x/10);
        putchar(x%10^48);
    }
    void writeln(ll x){write(x);putchar('\n');}
}
using namespace IO;

namespace Stack
{
    bool mark[N];
    int sz,head[N],nex[N*S],num[N*S];
    int top(int x){return num[head[x]];}
    void push(int x,int v){++sz;num[sz]=v;nex[sz]=head[x];head[x]=sz;}
    void pop(int x){while(mark[num[head[x]]])head[x]=nex[head[x]];}
    /*vector<int>st[N];
    int top(int x){return st[x].size()?*st[x].rbegin():0;}
    void push(int x,int v){st[x].push_back(v);}
    void pop(int x){while(st[x].size() && mark[*st[x].rbegin()])st[x].pop_back();}*/
}
using namespace Stack;

namespace Query
{
    struct Tquery
    {
        int v,l,r;
        Tquery(int _v=0,int _l=0,int _r=0):v(_v),l(_l),r(_r){}
    }q[N];
}
using namespace Query;

namespace Block
{
    int bi[N],bj[N],lpos[N],rpos[N];
    int x[S+5],y[S+5];
    struct Block
    {
        int pos,siz,tp,px,ps,las;
        int st[N],a[S+5],val[S+5];
        ll sum,ans[S+5];
        int &operator[](int x){return a[x];}
        int operator[](int x)const{return a[x];}
        ll resort()
        {
            ll res=0,lim=cnt>>8;
            for(int i=1;i<=siz;++i) val[i]=top(px+i),res+=(val[i]?0:a[i]);
            for(int i=0;i<=M;++i) x[i]=0;
            for(int i=1;i<=siz;++i) x[val[i]&M]++;
            for(int i=1;i<=M;++i) x[i]+=x[i-1];
            for(int i=siz;i;--i) y[x[val[i]&M]--]=val[i];
            for(int i=1;i<=lim;++i) x[i]=0;
            for(int i=1;i<=siz;++i) x[val[i]>>8]++;
            for(int i=1;i<=lim;++i) x[i]+=x[i-1];
            for(int i=siz;i;--i) val[x[y[i]>>8]--]=y[i];
            return res;
        }
        void build(){for(int i=1;i<=siz;++i)ans[0]+=a[i];sum=ans[0];}
        void rebuild()
        {
            ll trp=resort();las=st[tp];
            for(int i=siz-1;~i;--i)ans[i]=ans[i+1]+q[val[i+1]].v;ans[0]+=trp;
            ps=0;while(ps^siz && val[ps+1]<las)++ps;
            sum=ans[ps]+(ll)ps*q[las].v;
        }
        void cover(int id){st[++tp]=id;sum=(ll)siz*q[id].v;}
        void recover()
        {
            while(mark[st[tp]])--tp;int t=st[tp];
            if(t<=las)
            {
                while(ps && val[ps]>=t)--ps;
                sum=ans[ps]+(ll)ps*q[t].v;
            }
            else sum=(ll)siz*q[t].v;
        }
        ll calc(int l,int r)
        {
            int t=st[tp],v=q[t].v;ll res=0;
            if(!t){for(int i=l;i<=r;++i)res+=top(i)?q[top(i)].v:a[i-px];}
            else{for(int i=l;i<=r;++i)res+=top(i)<t?v:q[top(i)].v;}
            return res;
        }
    }b[N/S+5];
}
using namespace Block;

namespace solution
{
    ll query(int l,int r)
    {
        if(bi[l]==bi[r]) return b[bi[l]].calc(l,r);
        ll res=0;
        res+=b[bi[l]].calc(l,rpos[bi[l]]);
        res+=b[bi[r]].calc(lpos[bi[r]],r);
        for(int i=bi[l]+1;i<bi[r];++i) res+=b[i].sum;
        return res;
    }
    void update(int l,int r,int v)
    {
        if(bi[l]==bi[r]) 
        {
            for(int i=l;i<=r;++i)push(i,v);b[bi[l]].rebuild();
            return;
        }
        for(int i=l;bi[i]==bi[l];++i)push(i,v);b[bi[l]].rebuild();
        for(int i=r;bi[i]==bi[r];--i)push(i,v);b[bi[r]].rebuild();
        for(int i=bi[l]+1;i<bi[r];++i)b[i].cover(v);
    }
    void dele(int l,int r)
    {
        if(bi[l]==bi[r]) 
        {
            for(int i=l;i<=r;++i)pop(i);b[bi[l]].rebuild();
            return;
        }
        for(int i=l;bi[i]==bi[l];++i)pop(i);b[bi[l]].rebuild();
        for(int i=r;bi[i]==bi[r];--i)pop(i);b[bi[r]].rebuild();
        for(int i=bi[l]+1;i<bi[r];++i)b[i].recover();
    }
}
using namespace solution;

int main()
{
#ifndef ONLINE_JUDGE
    freopen("LGP5113.in","r",stdin);
    freopen("LGP5113.out","w",stdout);
#endif
    n=read();m=read();
    for(int i=1;i<=n;++i) bi[i]=(i-1)/S+1,bj[i]=(i-1)%S+1,b[bi[i]][bj[i]]=read();
    for(int i=1;i<=n;++i) rpos[bi[i]]=i;
    for(int i=n;i;--i) lpos[bi[i]]=i;
    for(int i=1;i<=bi[n];++i) b[i].px=lpos[i]-1,b[i].siz=S;
    b[bi[n]].siz=(n%S)?n%S:S;
    for(int i=1;i<=bi[n];++i) b[i].build();
    for(int i=1;i<=m;++i)
    {
        int op=read();
        if(op==1)
        {
            ++cnt;
            int l=read()^ans,r=read()^ans;q[cnt]=Tquery(read(),l,r);
            update(q[cnt].l,q[cnt].r,cnt);
        }
        else if(op==2)
        {
            int l=read()^ans,r=read()^ans;
            ans=query(l,r);writeln(ans);
        }
        else
        {
            int x=read()^ans;mark[x]=1;
            dele(q[x].l,q[x].r);
        }
    }
    return 0;
}

【总结】
分块得正解!暴力出奇迹!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值