zoukankan      html  css  js  c++  java
  • 题解 P4117 【[Ynoi2018]五彩斑斓的世界】

    原文链接

    第二分块,神仙大分块题,不过想清楚了后应该难度相对不太大。

    Part.1

    先看这题的数据范围(a_ileq 5e5),想到分块的方式应该与值域有关。另外我们可以发现,我们的值是只减小不增大的,考虑值域上应该有一个均摊,对每块做一个值域,考虑我们可以在这个上面搞一些事情做一些操作,使复杂度正确。然后我就不会了

    Part.2

    看了出题人的题解后我们可以发现有一个很有意思的trick,我们考虑值域是不增的,所以我们可以让值域最大值与最小值的差值越来越小。

    设当前修改操作有一个(x),当前最大值为(mx),我们分两种情况讨论。

    1. (mx le x imes 2) 此时我们把([x+1,mx])上的值暴力减。
    2. (mx > x imes 2)此时我们把([1,x])上的值暴力加,然后打上块整体减的(tag)

    我们每次总是使得块的最大值与最小值越来越近,所以一个块的总复杂的是(O(n))的,所有块的总复杂度是(O(nsqrt n))

    到目前为止,我们解决了最重要的操作。

    Part.3

    但询问时我们每个块如果只记录当前值在块内出现次数的话,很显然是不行的,因为零散块无法快速找到一个下标对应的现在的答案,所以我们考虑让值域的每个值记录她有哪些下标是当前值。或者换句话,对下标来说,就是用一个指针指向她当前在值域上的值。

    我们重新定义两个操作。

    1. (mx le x imes 2) 此时我们把([x+1,mx])上的值所包含的下标往([1,x])合并。
    2. (mx > x imes 2)此时我们把([1,x])上的值所包含的下标往([x+1,x imes 2])合并,然后打上块整体减的(tag)

    如果是零散块,我们就暴力找出当前下标指向的实际的值,修改后把整个块重构。这样是(O(sqrt n))的。

    什么样的数据结构能实现这样的操作呢?一个直接的想法就是链表,对每个值维护一个链表,合并是(O(1))的,满足我们的要求。

    然而我们还有一个更高效的数据结构,并查集。我们对每个值维护并查集,合并是(O(1))的,但在重构块时较链表常数更小,是更优秀的选择。

    Part.4

    说了这么多,我们还是过不了这道题。我没有玩你们

    仔细观察空间限制,发现是62.50MB果然前面还不够dl,我们要考虑一种线性空间的做法,其实很简单,有一个trick叫逐块处理,我们发现每个块对总的时间贡献了(O(n+m))的复杂度,那么我们对每个块跑一遍所有询问就可以了。

    那我们其他那么多题为什么没有用这个trick呢?因为用它要满足下面几个性质。

    1. 可以离线,显然我们是离线下来对每个块跑询问。
    2. 块之间不互相干扰。如果一个块的信息发生改变就要影响相邻或更多的块,那么就不能用。
    3. 答案可加性,我们是每次跑询问是把答案加入总答案,如果不可加,那么也不行。

    最后告诉你一个好消息,这个题不卡常!!!所以快乐的(code)吧!其实就是细节有点多

    还是贴一下我丑陋的代码吧

    inline int findf(int x) {return x==fa[x]?x:fa[x]=findf(fa[x]);}
    
    inline void merge(int u,int v)//合并两个值的下标
    {
        if(rt[v]) fa[rt[u]]=rt[v];
        else rt[v]=rt[u],to[rt[v]]=v;
        siz[v]+=siz[u];
        rt[u]=siz[u]=0;
    }
    
    void build(int x)//新建块
    {
        mxval=tag=0;
        for(int i=L[x];i<=R[x];++i)
        {
            mxval=max(mxval,a[i]);
            if(rt[a[i]]) fa[i]=rt[a[i]];
            else rt[a[i]]=fa[i]=i,to[i]=a[i];
            ++siz[a[i]];
        }
    }
    
    void restruct(int x,int l,int r,int nx)//重构块
    {
        for(int i=L[x];i<=R[x];++i)
            a[i]=to[findf(i)],rt[a[i]]=siz[a[i]]=0,a[i]-=tag;
        for(int i=L[x];i<=R[x];++i)
            to[i]=0;
        l=max(l,L[x]);r=min(r,R[x]);
        for(int i=l;i<=r;++i)
            a[i]=a[i]-(a[i]>nx)*nx;
        build(x);
    }
    
    void modify(int nx)//修改
    {
        if((nx<<1)<=mxval-tag)
        {
            for(int i=tag+1;i<=tag+nx;++i)
                if(rt[i]) merge(i,i+nx);
            tag+=nx;//记得修改tag
        }
        else
        {
            for(int i=tag+nx+1;i<=mxval;++i)
                if(rt[i]) merge(i,i-nx);
            if(tag+nx<mxval) mxval=tag+nx;//记得更新最大值
        }
    }
    
    void query(int x,int i)//询问
    {
        int l=q[i].l,r=q[i].r,nx=q[i].x;
        if(nx+tag>5e5) return;
        if(l<=L[x]&&R[x]<=r) ans[i]+=siz[nx+tag];
        else
        {
            l=max(L[x],l);r=min(R[x],r);
            for(int j=l;j<=r;++j)
                if(to[findf(j)]-tag==nx) ++ans[i];
        }
    }
    
    int main()
    {
        read(n);read(m);
        klen=sqrt(n);
        for(int i=1;i<=n;++i)
            read(a[i]);
        for(int i=1;i<=m;++i)
            read(q[i].opt),read(q[i].l),read(q[i].r),read(q[i].x);
        blocks=(n-1)/klen+1;
        for(int i=1;i<=blocks;++i)
            L[i]=R[i-1]+1,R[i]=i*klen;
        R[blocks]=n;
        for(int i=1;i<=blocks;++i)//先枚举块,逐块处理
        {
            memset(rt,0,sizeof(rt));
            memset(siz,0,sizeof(siz));
            build(i);
            for(int j=1;j<=m;++j)
            {
                if(L[i]>q[j].r||R[i]<q[j].l) continue;
                else if(q[j].opt==1)
                {
                    if(q[j].l<=L[i]&&R[i]<=q[j].r)
                        modify(q[j].x);
                    else restruct(i,q[j].l,q[j].r,q[j].x);
                }
                else query(i,j);
            }
        }
        for(int i=1;i<=m;++i)
            if(q[i].opt==2) printf("%d
    ",ans[i]);
        return 0;
    }
    
  • 相关阅读:
    Android详细的对话框AlertDialog.Builder使用方法 枫
    提高ASP.NET网站性能的方法 枫
    百度地图使用标注 枫
    C# 调用 .bat 文件的实现代码 枫
    mssqll2008下只显示相关的登陆操作 枫
    利用MSSQL排序规则,查询区分大小写的数据 枫
    Sql server 删除重复记录的SQL语句 枫
    open与fopen 文件描述符与文件指针
    android获得屏幕高度和宽度
    C语言:void swap(int &a, int &b);出错
  • 原文地址:https://www.cnblogs.com/gxm123/p/13604680.html
Copyright © 2011-2022 走看看