• 解题:由乃OI 2018 五彩斑斓的世界


    题面

    写在前面的扯淡:

    分块的总体学习告一段落,这算是分块集中学习的最后一题么;以后当然也可能会写,就是零零散散的题了=。=

    在洛谷上搜ynoi发现好像只有这道题和 由乃OI 2018 未来日记 是分块,久闻由乃OI之大名,就想试着写一写这道题(那道太毒了,写不了);结果一开始差点被吓跑了,不过最后还是硬着头皮写完了QAQ

    “分块最重要的就是常数” — —shadowice1984

    好像挺有道理的,分块从诞生之日起就是一个依靠你的决策而决定复杂度的算法(或思想),它不像什么高级的数据结构每块的写法都是基本固定的,复杂度有着严格的证明— —分块是个相对灵活的算法(所以也说它是一种思想)。举个例子:最简单的那种序列分块基本上复杂度都是$O(frac{n^2}{size})$,$size$就是你自己决定的块大小,大家可能都知道块大小开$sqrt(n)$(当然这是均值不等式算出来的),但是有时候可能某个操作的常数非常大,把块大小适当地调整可能会跑的更快......但是当卡常超过一定的地步,算法就又失去了本身的意义和美感......算了我语言表达能力太差了,还是赶快写题解吧=。=

    考虑到区间一个个查询修改非常慢,先用并查集把每块里相同的数字连到第一次出现的位置上,然后考虑修改操作$(l,r,v)$:

    对于一个最大值$maxx$不超过$2*v$的块,我们直接修改,即把大于$v$而小于最大值的部分的每个数$x$用并查集连到$x-v$

    对于一个最大值$maxx$大于$2*v$的块,我们反过来修改,把小于$v$的部分的每个数$x$连到$x+v$,同时在区间上打标记

    为什么要这样做?

    首先我们发现这两种修改方法本质是一样的

    然后我们发现对于第一种修改我们每次动的数是$v$级别的,同时我们把最大值缩小了$v$

    而对于第二种修改我们每次动的数是$maxx-v$级别的,在那个限制下其实还是$v$级别的,和上面的效率是一样的(注意我们是根据$maxx$分出两种情况的,这也是用到了分块的思想)

    然后均摊复杂度就有保证了,当然你还需要卡常(其实卡常不是很厉害,我就用了烂大街的快读+快输+register跑过去问题不大,还有这题当年在CF上好像因为CF太快+优化+3s实现把暴力放过去了=。=)

      1 #include<cmath>
      2 #include<cstdio>
      3 #include<cctype>
      4 #include<cstring>
      5 #include<algorithm>
      6 using namespace std;
      7 const int N=100005,Sq=320,M=100000;
      8 int a[N],ori[N],blo[N],ll[N],rr[N],aset[N];
      9 int siz[N],maxx[Sq],laz[Sq],firs[Sq][N];
     10 int n,m,t1,t2,t3,t4,sqr,cnt;
     11 inline int read()
     12 {
     13     int ret=0;
     14     char ch=getchar();
     15     while(!isdigit(ch))
     16         ch=getchar();
     17     while(isdigit(ch))
     18         ret=(ret<<3)+(ret<<1)+(ch^48),ch=getchar();
     19     return ret;    
     20 } 
     21 void write(int x)
     22 {
     23     if(x>9) write(x/10);
     24     putchar(x%10+48);
     25 }
     26 int finda(int x)
     27 {
     28     return (x==aset[x])?x:aset[x]=finda(aset[x]);
     29 }
     30 inline void rebuild(int b)//块重构 
     31 {
     32     register int i;
     33     for(i=ll[b];i<=rr[b];i++) 
     34     {
     35         maxx[b]=max(maxx[b],a[i]);
     36         firs[b][a[i]]=0,aset[i]=i,siz[i]=1;
     37     }
     38     for(i=ll[b];i<=rr[b];i++)//把每个数都并到第一次出现的位置 
     39     {
     40         int &fir=firs[b][a[i]];
     41         if(fir) siz[fir]+=siz[i],aset[i]=fir;
     42         else fir=i,ori[i]=a[i];
     43     }
     44 }
     45 inline void force(int b,int l,int r,int v)//大力重构 
     46 {
     47     register int i;
     48     for(i=ll[b];i<=rr[b];i++)
     49         firs[b][a[i]=ori[finda(i)]]=0;
     50     for(i=l;i<=r;i++)
     51         if(a[i]-laz[b]>v) a[i]-=v;
     52     rebuild(b);
     53 }
     54 void change(int l,int r,int v)
     55 {
     56     register int i,j;
     57     int b1=blo[l],b2=blo[r];
     58     if(b1!=b2)
     59     {
     60         force(b1,l,rr[b1],v);
     61         force(b2,ll[b2],r,v); 
     62         for(i=b1+1;i<=b2-1;i++)
     63             if(maxx[i]-laz[i]<2*v)//当最大值不超过2*v时,正常地修改 
     64             {
     65                 for(j=laz[i]+v+1;j<=maxx[i];j++)
     66                 {
     67                     int &pos1=firs[i][j];
     68                     int &pos2=firs[i][j-v];
     69                     if(pos1)
     70                     {
     71                         if(pos2) siz[pos2]+=siz[pos1],aset[pos1]=pos2;
     72                         else pos2=pos1,ori[pos2]=j-v; pos1=0;
     73                     }
     74                 }
     75                 maxx[i]=min(maxx[i],laz[i]+v);
     76             }
     77             else//否则反过来,把其余的数加上这个值并打标记
     78             {
     79                 for(j=laz[i]+1;j<=laz[i]+v;j++)
     80                 {
     81                     int &pos1=firs[i][j];
     82                     int &pos2=firs[i][j+v];
     83                     if(pos1)
     84                     {
     85                         if(pos2) siz[pos2]+=siz[pos1],aset[pos1]=pos2;
     86                         else pos2=pos1,ori[pos2]=j+v; pos1=0;
     87                     }
     88                 }
     89                 laz[i]+=v;
     90             }
     91     }
     92     else force(b1,l,r,v);
     93     //修改的理论依据:对于第一种情况最大值在O(v)时间内减小了v,对于第二种情况最大值在O(max-v)减少了max-v,所以最终的均摊复杂度是O(1)的 
     94     //(虽然CF的官方题解这里写的是极差,但我觉得不太对,例如对233,235两个数来说,将大于234的数减去234后极差反而在增大) 
     95 }
     96 int query(int l,int r,int v)//普通的查询 
     97 {
     98     register int i;
     99     int b1=blo[l],b2=blo[r],ret=0;
    100     if(b1!=b2)
    101     {
    102         for(i=l;i<=rr[b1];i++)
    103             ret+=(ori[finda(i)]-laz[b1]==v);
    104         for(i=ll[b2];i<=r;i++)
    105             ret+=(ori[finda(i)]-laz[b2]==v);
    106         for(i=b1+1;i<=b2-1;i++)
    107             if(laz[i]+v<=M) ret+=siz[firs[i][laz[i]+v]];
    108     }
    109     else
    110         for(i=l;i<=r;i++)
    111             ret+=(ori[finda(i)]-laz[b1]==v);
    112     return ret;
    113 }
    114 int main ()
    115 {
    116     register int i;
    117     n=read(),m=read();
    118     sqr=sqrt(n)+5,ll[cnt=1]=1;
    119     for(i=1;i<=n;i++)
    120     {
    121         a[i]=read(),ori[i]=a[i];
    122         aset[i]=i,blo[i]=(i-1)/sqr+1;
    123         maxx[blo[i]]=max(maxx[blo[i]],a[i]);
    124         if(i%sqr==0) rr[cnt++]=i,ll[cnt]=i+1; 
    125     }
    126     rr[cnt]=n;
    127     for(i=1;i<=cnt;i++) rebuild(i);
    128     for(i=1;i<=m;i++)
    129     {
    130         t1=read(),t2=read(),t3=read(),t4=read();
    131         if(t1==1) change(t2,t3,t4); else printf("%d
    ",query(t2,t3,t4));
    132     } 
    133     return 0;
    134 }
    View Code
  • 相关阅读:
    Serilog 动态添加自定义属性
    C# 序列化与反序列化
    幂等设计
    服务无状态
    vue 显示 mysql 数据库表 Demo
    C# 调用 linux 函数 —— Linux 头文件目录位置
    创建可以在 Zynq 上运行的动态库
    C# 获取所在函数名
    Linux 关闭终端不结束进程
    C# 自动生成版本号
  • 原文地址:https://www.cnblogs.com/ydnhaha/p/9963914.html
Copyright © 2020-2023  润新知