【分块】BZOJ5145 [Ynoi2018] 未来日记

【前言】
年初一写的题,原因是熬夜太晚了,写几题分块醒醒神(雾

【题目】
BZOJ
长度为 n n n序列 A A A,支持 m m m次两种操作:

  • 将区间内值为 x x x的位置修改为 y y y
  • 询问区间第 k k k
    n , m , A i ≤ 1 0 5 n,m,A_i\leq 10^5 n,m,Ai105

【解题思路】
不可做题考虑分块暴力。
先考虑如何求区间第 k k k小。我们对序列和权值都进行分块,设 n u m i , j num_{i,j} numi,j表示在序列前 j j j块中,在权值前 i i i块内的数字个数, c n t i , j cnt_{i,j} cnti,j表示序列前 j j j块中数字 i i i出现次数。对于一个询问 [ l , r ] [l,r] [l,r],先将散块加入临时数组 t n u m , t c n t tnum,tcnt tnum,tcnt中,然后枚举答案在哪一个权值块中,接着暴力枚举答案可以在 O ( n ) O(\sqrt n) O(n )的时间内求出答案。

接下来考虑如何实现区间改值。对于散块我们可以直接暴力重构整块。对于整块,若这一块中没有 x x x可以跳过,若没有 y y y,则直接将 x x x映射成 y y y,若两者都有,则暴力重构整块。

暴力重构?我们考虑什么情况下会重构,即在一个块内发生了一次合并。长度为 n n n的序列最多只会合并 O ( n ) O(n) O(n)次(因为会使不同的数字个数 − 1 -1 1),而每次修改对两个散块各提供了一次合并机会。那么总的合并次数就是 O ( n + m ) O(n+m) O(n+m)的了。

具体实现上,每块中的转换情况是若干条不相交的链,只需要记录每个初值转换后是什么,以及每个现在的值对于哪个初值即可。查询的时候我们需要知道散块每个位置的值,这里可以直接重构两块,再遍历一遍原数组即可。修改时还要维护 n u m , c n t num,cnt num,cnt,由于只有两个值, j j j这一维是可以暴力维护的。

最后复杂度 O ( ( n + m ) n ) O((n+m)\sqrt n) O((n+m)n )

写一下,调一年。luogu卡不过告辞
我又写了奇怪的内存出来。。。一定要注意了!!!

【参考代码】

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

const int N=1e5+10,lim=320,Blo=lim+5;

namespace IO
{
    inline char gc()
    {
    	static char buf[100000],*p1=buf,*p2=buf;
    	return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
    }
    inline int read()
    {
        int ret=0;char c=gc();
        while(!isdigit(c)) c=gc();
        while(isdigit(c)) ret=ret*10+(c^48),c=gc();
        return ret;
    }
    inline void write(int x){if(x>9)write(x/10);putchar(x%10^48);}
    inline void writeln(int x){write(x);putchar('\n');}
}
using namespace IO;

namespace DreamLolita
{
    int n,m;
    int a[N],bl[N];
    int pos[N],num[Blo][Blo],cnt[Blo][N],tcnt[N],tnum[Blo];//use overflow memory because like cnt[lim][N] 
    struct Block
    {
        int st,ed,id[N],rid[Blo];
        void init()//get real number
        {
            for(int i=st;i<=ed;++i) a[i]=rid[pos[i]];
        }
        void change(int x,int y){int uid=id[x];id[y]=uid;rid[uid]=y;id[x]=0;}
        void build()//init id
        {
            for(int i=1;i<=lim;++i) id[rid[i]]=0;
            for(int i=st,ind=0;i<=ed;++i) if(!id[a[i]]) 
                id[a[i]]=++ind,rid[ind]=a[i];
            for(int i=st;i<=ed;++i) pos[i]=id[a[i]];
        }
    }B[lim];
    void rebuild(int p,int x,int y)//get the prefix sum
    {
        //cerr<<p<<" "<<x<<" "<<y<<endl;
        for(int i=bl[p];i<=bl[n];++i) 
        {
            num[i][bl[x]]+=num[i-1][bl[x]];num[i][bl[y]]+=num[i-1][bl[y]];
            cnt[i][x]+=cnt[i-1][x];cnt[i][y]+=cnt[i-1][y];
        }
    }
    void brute(int l,int r,int x,int y)//update the sporadic blocks && the changed blocks
    {
        for(int i=l;i<=r;++i) if(a[i]==x)
        {
            a[i]=y;
            --num[bl[l]][bl[x]];++num[bl[l]][bl[y]];
            --cnt[bl[l]][x];++cnt[bl[l]][y];
        }
    }
    void update(int l,int r,int x,int y)
    {
        if(cnt[bl[r]][x]-cnt[bl[l]-1][x]==0) return;
        for(int i=bl[n];i>=bl[l];--i)
        {
            num[i][bl[x]]-=num[i-1][bl[x]];num[i][bl[y]]-=num[i-1][bl[y]];
            cnt[i][x]-=cnt[i-1][x];cnt[i][y]-=cnt[i-1][y];
        }//undo the prefix sum
        if(bl[l]==bl[r])
        {
            B[bl[l]].init();brute(l,r,x,y);
            B[bl[l]].build();rebuild(l,x,y);
            return;
        }
        B[bl[l]].init();B[bl[r]].init();
        brute(l,B[bl[l]].ed,x,y);brute(B[bl[r]].st,r,x,y);
        B[bl[l]].build();B[bl[r]].build();
        for(int i=bl[l]+1;i<bl[r];++i)
        {
            if(!cnt[i][x]) continue;
            if(!cnt[i][y])
            {
                num[i][bl[y]]+=cnt[i][x];num[i][bl[x]]-=cnt[i][x];
                cnt[i][y]+=cnt[i][x];cnt[i][x]=0;
                B[i].change(x,y);
            }//map x to to y
            else
            {
                B[i].init();brute(B[i].st,B[i].ed,x,y);B[i].build();
            }//bruteforce to merge two number
        }//update the integral blocks
        rebuild(l,x,y);
    }
    int query(int l,int r,int k)
    {
        int res=0,tot=0;
        if(bl[l]==bl[r])
        {
            B[bl[l]].init();for(int i=l;i<=r;++i)tcnt[i]=a[i];
            nth_element(tcnt+l,tcnt+l+k-1,tcnt+r+1);res=tcnt[l+k-1];
            for(int i=l;i<=r;++i) tcnt[i]=0; 
            return res;
        }
        B[bl[l]].init();B[bl[r]].init();
        for(int i=l;i<=B[bl[l]].ed;++i) ++tcnt[a[i]],++tnum[bl[a[i]]];
        for(int i=B[bl[r]].st;i<=r;++i) ++tcnt[a[i]],++tnum[bl[a[i]]];
        for(int i=1;i<=bl[N-1];++i) 
        {
            if(tot+tnum[i]+num[bl[r]-1][i]-num[bl[l]][i]>=k)
            {
                for(int j=B[i].st;j<=B[i].ed;++j)
                {
                    if(tcnt[j]+cnt[bl[r]-1][j]-cnt[bl[l]][j]+tot>=k){res=j;goto OK;}
                    else tot+=tcnt[j]+cnt[bl[r]-1][j]-cnt[bl[l]][j];
                }
            }
            else tot+=tnum[i]+num[bl[r]-1][i]-num[bl[l]][i];
        }
        OK:
        for(int i=l;i<=B[bl[l]].ed;++i) --tcnt[a[i]],--tnum[bl[a[i]]];
        for(int i=B[bl[r]].st;i<=r;++i) --tcnt[a[i]],--tnum[bl[a[i]]];
        return res;
    }
    void solution()
    {
        for(int i=1;i<N;++i) bl[i]=(i-1)/lim+1;
        n=read();m=read();
        for(int i=1;i<=n;++i) a[i]=read();
        for(int i=1;i<=bl[n];++i) B[i].st=(i-1)*lim+1,B[i].ed=i*lim; B[bl[n]].ed=n;
        for(int i=1;i<=bl[n];++i) B[i].build();
        for(int i=1;i<=bl[n];++i)
        {
            memcpy(num[i],num[i-1],sizeof(num[i]));
            memcpy(cnt[i],cnt[i-1],sizeof(cnt[i]));
            for(int j=B[i].st;j<=B[i].ed;++j) ++num[i][bl[a[j]]],++cnt[i][a[j]];
        }
        while(m--)
        {
            int op=read(),l=read(),r=read(),x,y;
            if(op&1) x=read(),y=read(),update(l,r,x,y);
            else x=read(),writeln(query(l,r,x));
        }
    }
}

int main()
{
#ifndef ONLINE_JUDGE
    freopen("BZOJ5145.in","r",stdin);
    freopen("BZOJ5145.out","w",stdout);
#endif
    DreamLolita::solution();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值