BZOJ2453: 维护队列(分块)

传送门

  • 题意:

给一个序列,每个位置对应一个值,支持下面两种操作:
1.修改某个位置的值。
2.询问(l,r)区间内不同值的个数。

  • 题解:

1.考虑分块:统计ans[i][j]表示第i块到第j块的个数,容易发现对于每个询问,只需查看完整块的个数和不完整块的影响。时间复杂度 O(nn) 。但修改会修改两两块之间的ans[i][j],时间复杂度 O(nm) (这道题修改只有1000个,可以过去)。

2.莫队算法:如果没有修改操作莫队直接秒,有修改操作写莫队就有点麻烦,要加一个时间的维度。具体不再赘述,可以尝试一下,也可以过。

3.还是考虑分块:对于每一个位置记录前面相同颜色最近出现的位置pre[i]。对于一个查询就是查询块中pre[i]小于l的个数。分块并排序,块内二分查找。时间复杂度 O(nnlogn) 。修改操作暴力修改,如果不同就重建。(反正只改一个位置,最多也只重建前后影响的几个块)。时间复杂度 m(n+nlogn)

第一种和第三种本质上是差不多的,但第一种难写一点,这里给出第三种的代码:

  • Code
#include<bits/stdc++.h>
using namespace std;
const int Maxn=3e4+50;
const int Maxm=3e4+50;
int S=140;

inline int read()
{
    char ch=getchar();int i=0,f=1;
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){i=(i<<1)+(i<<3)+ch-'0';ch=getchar();}
    return i*f;
}

int n,m,a[Maxn],bg[Maxn],ed[Maxn],s1,pre[Maxn],last[Maxn],s,b[Maxn];
char Ch[2];

struct node
{
    int val,id,op;
    friend inline bool operator <(const node &a,const node &b){return a.val<b.val;}
}B[Maxn+Maxm];

struct Node
{
    int pos,val,op;
}q[Maxn];

inline void l_b()
{
    sort(B+1,B+s1+1);
    int t=0;
    for(int i=1;i<=s1;i++)
    {
        if(i==1||B[i].val!=B[i-1].val)t++;
        if(B[i].op)a[B[i].id]=t;
        else q[B[i].id].val=t;
    }
}

inline void Pre()
{
    for(int i=1;i<=n;i++)
    {
        pre[i]=last[a[i]];
        last[a[i]]=i;
    }
    for(int i=1;i*S<=n;i++)s=i,bg[i]=ed[i-1]+1,ed[i]=ed[i-1]+S;
    if(ed[s]!=n)s++,bg[s]=ed[s-1]+1,ed[s]=n;
    for(int i=1;i<=n;i++)b[i]=pre[i];
    for(int i=1;i<=s;i++)sort(b+bg[i],b+ed[i]+1);
}

int buf[50];
inline void W(int x)
{
    if(!x)putchar('0');
    if(x<0)putchar('-'),x=-x;
    while(x)buf[++buf[0]]=(x%10),x/=10;
    while(buf[0])putchar('0'+buf[buf[0]--]);
}

inline int calc(int now,int val)
{
    int l=bg[now],r=ed[now],L;
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(b[mid]<val)l=mid+1,L=mid;
        else r=mid-1;
    }
    if(r==bg[now]-1)L=bg[now]-1;
    return L-bg[now]+1;
}

inline void rebuild(int x)
{
    for(int i=bg[x];i<=ed[x];i++)b[i]=pre[i];
    sort(b+bg[x],b+ed[x]+1);
}

int main()
{
    n=read(),m=read();
    for(int i=1;i<=n;i++)B[++s1].val=read(),B[s1].id=i,B[s1].op=1;
    for(int i=1;i<=m;i++)
    {
        scanf("%s",Ch+1);
        if(Ch[1]=='Q')q[i].op=0;
        else q[i].op=1;
        q[i].pos=read();
        q[i].val=read();
        if(Ch[1]=='R')
        {
            B[++s1].val=q[i].val;
            B[s1].op=0;
            B[s1].id=i;
        }
    }
    l_b();
    Pre();
    for(int i=1;i<=m;i++)
    {
        if(q[i].op)
        {
            for(int j=1;j<=n;j++){last[a[j]]=0;}
            a[q[i].pos]=q[i].val;
            for(int j=1;j<=n;j++)
            {
                int t=pre[j]; 
                pre[j]=last[a[j]];
                if(pre[j]!=t)rebuild((j-1)/S+1);
                last[a[j]]=j;
            }
        }
        else
        {
            int l=q[i].pos,r=q[i].val;
            int ans=0;
            if (r-l+1>2*S)
            {
                int L=(l-1)/S+1,R=(r-1)/S+1;
                if(bg[L]!=l)L++;
                if(ed[R]!=r)R--;
                for(int i=L;i<=R;i++)ans+=calc(i,l);
                for(int i=l;i<bg[L];i++)if(pre[i]<l)ans++;
                for(int i=ed[R]+1;i<=r;i++)if(pre[i]<l)ans++;
                W(ans);putchar('\n');
            }
            else
            {
                for(int i=l;i<=r;i++)if(pre[i]<l)ans++;
                W(ans);putchar('\n');
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值