zoukankan      html  css  js  c++  java
  • 分块

      今天决定要学分块!

      于是就先开这么一篇blog吧.

      看了一眼数列分块9题,吓哭了~分块这么有用的吗

      

      数列分块1:https://loj.ac/problem/6277

      从这道题开始数列分块的旅程~

      题意概述:长度为$50000$的序列,区间加,单点查询.

      分块其实就是暴力...只不过经过一番玄学分析复杂度竟然还很不错。我们将序列分成$m$块,每次修改时对于整块直接打标记,对于散块暴力修改,显然当$m=sqrt{n}$的时候两种修改的复杂度都是比较优秀的。因为每次最多对于$sqrt{n}$个整块打标记,零散部分最多也只有$2*sqrt{n}$个.既然分块本来就比较暴力,那就多卡卡常吧,这里提供一个很科学的卡常方法,对于两边的零散部分,如果发现要修改的长度大于块长的一半,就给整个块打上修改标记,再把范围外的多余部分减去.这样算出来零散部分就最多只有$sqrt{n}$个了.这个卡常的效果极其不明显...码量却多了好多.总复杂度$O(Nsqrt{N})$

       

      
     1 # include <cstdio>
     2 # include <iostream>
     3 # include <cmath>
     4 # define R register int
     5 
     6 using namespace std;
     7 
     8 const int maxn=50004;
     9 int l,r,c,opt,n,siz;
    10 int a[maxn],b[maxn],delta[maxn];
    11 
    12 void add (int l,int r,int v)
    13 {
    14     if(b[l]==b[r])
    15     {
    16         for (R i=l;i<=r;++i)
    17             a[i]+=v;
    18     }
    19     else
    20     {
    21         if(b[l]*siz-l>l-(1+b[l]*siz-siz))
    22         {
    23             for (R i=1+b[l]*siz-siz;i<l;++i) a[i]-=v;
    24             delta[ b[l] ]+=v;
    25         }
    26         else
    27             for (R i=l;i<=b[l]*siz;++i) a[i]+=v;
    28         for (R i=b[l]+1;i<=b[r]-1;++i) delta[i]+=v;
    29         if(b[r]*siz-r>r-(1+b[r]*siz-siz))
    30             for (R i=1+b[r]*siz-siz;i<=r;++i) a[i]+=v;
    31         else
    32         {
    33             for (R i=r+1;i<=b[r]*siz;++i) a[i]-=v;
    34             delta[ b[r] ]+=v;
    35         }
    36     }
    37 }
    38 
    39 int main()
    40 {
    41     scanf("%d",&n);
    42     for (R i=1;i<=n;++i) scanf("%d",&a[i]);
    43     siz=sqrt(n);
    44     for (R i=1;i<=n;++i) b[i]=(i-1)/siz+1;
    45     for (R i=1;i<=n;++i)
    46     {
    47         scanf("%d%d%d%d",&opt,&l,&r,&c);
    48         if(opt==0)
    49             add(l,r,c);
    50         else
    51             printf("%d
    ",a[r]+delta[ b[r] ]);
    52     }
    53     return 0;
    54 }
    数列分块1

     

      数列分块2:https://loj.ac/problem/6278

      题意概述:长度为$50000$的序列,区间加,区间查询小于某个数的数字个数。

      本来以为至少要树套树,没想到分块这么神奇。

      对于每一块先排序,散的部分暴力找,其他部分在块内二分。注意!因为排完序后块内的下标就不连续了,所以不能再按照下标修改,散块必须全部遍历来修改。修改完后再次排序以保证有序性。  

      
      1 # include <cstdio>
      2 # include <iostream>
      3 # include <cmath>
      4 # include <algorithm>
      5 # define R register int
      6 # define ll long long
      7 # define P(i) sort(a+1+siz*(i-1),a+1+min(siz*i,n),cmp)
      8 
      9 using namespace std;
     10 
     11 const int maxn=100004;
     12 int id,n,siz,opt,l,r,c,ans;
     13 ll delta[maxn];
     14 struct dig
     15 {
     16     int b,k;
     17     ll v;
     18 }a[maxn];
     19 
     20 bool cmp (dig a,dig b) { return a.v<b.v; }
     21 
     22 void add (int l,int r,ll c)
     23 {
     24     if(a[l].b==a[r].b)
     25     {
     26         id=a[l].b;
     27         for (R i=1+siz*(id-1);i<=min(id*siz,n);++i)
     28             if(a[i].k>=l&&a[i].k<=r) a[i].v+=c;
     29         P(a[l].b);
     30     }
     31     else
     32     {
     33         id=a[l].b;
     34         for (R i=1+siz*(id-1);i<=min(siz*id,n);++i)
     35             if(a[i].k>=l&&a[i].k<=r)
     36                 a[i].v+=c;
     37         P(id);
     38         for (R i=a[l].b+1;i<=a[r].b-1;++i) delta[i]+=c;
     39         id=a[r].b;
     40         for (R i=1+siz*(id-1);i<=min(siz*id,n);++i)
     41             if(a[i].k>=l&&a[i].k<=r)
     42                 a[i].v+=c;
     43         P(id);
     44     }
     45 }
     46 
     47 int check (int x,ll c)
     48 {
     49     int l=1+siz*(x-1),r=min(n,siz*x),mid,ans=siz*(x-1);
     50     while (l<=r)
     51     {
     52         mid=r+((l-r)>>1);
     53         if(a[mid].v+delta[ a[mid].b ]<c)
     54             l=mid+1,ans=max(ans,mid);
     55         else
     56             r=mid-1;            
     57     }
     58     return ans-siz*(x-1);
     59 }
     60 
     61 int ask (int l,int r,ll c)
     62 {
     63     int ans=0,id;
     64     if(a[l].b==a[r].b)
     65     {
     66         id=a[l].b;
     67         for (R i=1+siz*(id-1);i<=min(siz*id,n);++i)
     68             if(a[i].k>=l&&a[i].k<=r&&a[i].v+delta[ a[i].b ]<c)
     69                 ans++;
     70     }
     71     else
     72     {
     73         id=a[l].b;
     74         for (R i=1+siz*(id-1);i<=min(siz*id,n);++i)
     75             if(a[i].k>=l&&a[i].k<=r&&a[i].v+delta[ a[i].b ]<c)
     76                 ans++;
     77         for (R i=a[l].b+1;i<=a[r].b-1;++i)
     78             ans+=check(i,c);    
     79         id=a[r].b;
     80         for (R i=1+siz*(id-1);i<=min(siz*id,n);++i)
     81             if(a[i].k>=l&&a[i].k<=r&&a[i].v+delta[ a[i].b ]<c)
     82                 ans++;
     83     }
     84     return ans;
     85 }
     86 
     87 int main()
     88 {
     89     scanf("%d",&n);
     90     siz=sqrt(n);
     91     for (R i=1;i<=n;++i)
     92     {
     93         scanf("%lld",&a[i].v);
     94         a[i].k=i;
     95         a[i].b=(i-1)/siz+1;
     96     }
     97     for (R i=1;i<=a[n].b;++i) P(i);
     98     for (R i=1;i<=n;++i)
     99     {
    100         scanf("%d%d%d%d",&opt,&l,&r,&c);
    101         if(opt)
    102         {            
    103             ans=ask(l,r,1LL*c*c);
    104             printf("%d
    ",ans);
    105         }
    106         else 
    107             add(l,r,c);
    108     }
    109     return 0;
    110 }
    数列分块2

      

      数列分块3:https://loj.ac/problem/6279

      数列分块4:https://loj.ac/problem/6280

      题意概述:长度为$50000$的序列,区间加,区间求和.

      和$1$是一样的思路,对于每一个块再记录一个$sum$,整块一起加,散的部分暴力加即可.

      
     1 # include <cstdio>
     2 # include <iostream>
     3 # include <cmath>
     4 # include <algorithm>
     5 # define R register int
     6 
     7 using namespace std;
     8 
     9 const int maxn=50004;
    10 int n,b[maxn],opt,l,r,c,siz;
    11 long long a[maxn],delta[maxn],s[maxn];
    12 
    13 void add (int l,int r,int c)
    14 {
    15     if(b[l]==b[r]) for (R i=l;i<=r;++i) a[i]+=c,s[ b[i] ]+=c;
    16     else
    17     {
    18         for (R i=l;i<=siz*b[l];++i) a[i]+=c,s[ b[i] ]+=c;
    19         for (R i=b[l]+1;i<=b[r]-1;++i) delta[i]+=c,s[i]+=siz*c;
    20         for (R i=1+siz*(b[r]-1);i<=r;++i) a[i]+=c,s[ b[i] ]+=c;
    21     }
    22 }
    23 
    24 int ask (int l,int r,int mod)
    25 {
    26     long long ans=0;
    27     if(b[l]==b[r]) for (R i=l;i<=r;++i) ans=(ans+a[i]+delta[ b[i] ])%mod;
    28     else
    29     {
    30         for (R i=l;i<=siz*b[l];++i) ans=(ans+a[i]+delta[ b[i] ])%mod;
    31         for (R i=b[l]+1;i<=b[r]-1;++i) ans=(ans+s[i])%mod;
    32         for (R i=1+siz*(b[r]-1);i<=r;++i) ans=(ans+a[i]+delta[ b[i] ])%mod;
    33     }
    34     return ans%mod;
    35 }
    36 
    37 int main()
    38 {
    39     scanf("%d",&n);
    40     siz=sqrt(n);
    41     for (R i=1;i<=n;++i) scanf("%lld",&a[i]);
    42     for (R i=1;i<=n;++i) b[i]=(i-1)/siz+1,s[ b[i] ]+=a[i];
    43     for (R i=1;i<=n;++i)
    44     {
    45         scanf("%d%d%d%d",&opt,&l,&r,&c);
    46         if(opt) printf("%d
    ",ask(l,r,c+1));
    47         else add(l,r,c);
    48     }
    49     return 0;
    50 }
    数列分块4

     

      数列分块5:https://loj.ac/problem/6281

      数列分块6:https://loj.ac/problem/6282

      数列分块7:https://loj.ac/problem/6283

      数列分块8:https://loj.ac/problem/6284

      数列分块9:https://loj.ac/problem/6285

      普通计算姬:https://www.lydsy.com/JudgeOnline/problem.php?id=4765

      给定一棵有根带权树,要求支持单点修改及一个区间内所有点的子树和的和。

      $n,m<=10^5$

      首先从网上抄一个简单的做法:

      首先根据编号分块,并预处理一个数组 $a[i][j] $ 表示第 $i$ 个点的值对第 $j$ 个块内答案的影响,查询时整块直接用算好的答案,零散的部分利用 $dfs$ 序一个一个地算子树和,又快又好写。$O(Msqrt{N}logN)$

      但是这不是重点。

      着重讲一下我的又慢又难写的做法:

      每个点会影响哪些点的子树和呢?显然是它的祖先们啦。

      那么修改一个点,影响的就是一条链,想到了树链剖分。

      首先对于原树进行链剖,按照dfs序分块,块内以原编号为关键字排序。平常写的链剖套线段树居多,但是这里使用链剖套分块,修改比较显然。查询时遍历每一个块,用二分求出块内哪部分包含于询问区间,因为块内排过序所以这个部分是连续的,块内再使用树状数组求前缀和。看起来像是两个log过不了,但是有一个log是套在根号外面的,所以勉勉强强卡过了。看起来并不是很复杂,但是写起来是真的复杂...放在这里只是一个纪念,如果能想到第一种还是写第一种比较好。

      $O(M imessqrt{N} imes logN imes logsqrt{N})$

      
      1 # include <cstdio>
      2 # include <iostream>
      3 # include <cstring>
      4 # include <algorithm>
      5 # include <cmath>
      6 # define R register int
      7 # define ULL unsigned long long
      8 
      9 using namespace std;
     10 
     11 const int N=100005;
     12 const int B=320;
     13 int n,m,a,b,rt,firs[N],h,bel[N],pos,opt,l,r,v,d[N];
     14 int dep[N],siz[N],son[N],Tp[N],id[N],f[N],cnt;
     15 int old[N]; //dfs序为i的点的原编号为old[i] 
     16 int block_cnt,block_siz;
     17 struct node { int old_id,dfs_id; };
     18 struct edge { int too,nex; }g[N<<1];
     19 struct block
     20 {
     21     int l,r,siz; //dfs序的左右,其实也就是把块全部铺开后所占有的区间
     22     int delta; //整块的标记,没有必要下传
     23     ULL t[B]; //块内的前缀和(BIT)
     24     node e[B]; //块内元素
     25 }k[B]; //每个块保存dfs编号连续的一段元素,按照原序号排列
     26 ULL ans,s[N];
     27 
     28 inline int read()
     29 {
     30     R x=0;
     31     char c=getchar();
     32     while (!isdigit(c)) c=getchar();
     33     while (isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
     34     return x;
     35 }
     36 
     37 bool cmp (node a,node b) { return a.old_id<b.old_id; }
     38 
     39 void dfs1 (int x)
     40 {
     41     siz[x]=1;
     42     s[x]=d[x];
     43     int j,maxs=-1;
     44     for (R i=firs[x];i;i=g[i].nex)
     45     {
     46         j=g[i].too;
     47         if(dep[j]) continue;
     48         f[j]=x,dep[j]=dep[x]+1;
     49         dfs1(j);
     50         siz[x]+=siz[j];
     51         s[x]+=s[j];
     52         if(siz[j]>=maxs) maxs=siz[j],son[x]=j;
     53     }
     54 }
     55 
     56 void dfs2 (int x,int tp)
     57 {
     58     id[x]=++cnt;
     59     old[ id[x] ]=x;
     60     Tp[x]=tp;
     61     if(!son[x]) return ;
     62     dfs2(son[x],tp);
     63     int j;
     64     for (R i=firs[x];i;i=g[i].nex)
     65     {
     66         j=g[i].too;
     67         if(son[x]==j||f[x]==j) continue;
     68         dfs2(j,j);
     69     }
     70 }
     71 
     72 inline void add (int x,int y)
     73 {
     74     g[++h].nex=firs[x];
     75     g[h].too=y;
     76     firs[x]=h;
     77 }
     78 
     79 void ad (int t,int pos,ULL v) { for (R i=pos;i<=k[t].siz;i+=(i&(-i))) k[t].t[i]+=v; }
     80 
     81 ULL query (int t,int pos)
     82 {
     83     ULL ans=0;
     84     for (R i=pos;i;i-=(i&(-i))) ans+=k[t].t[i];
     85     return ans;
     86 }
     87 
     88 void add (int a,int b,ULL v)
     89 {
     90     int l,r,id;
     91     for (R i=1;i<=block_cnt;++i)
     92     {
     93         l=k[i].l; r=k[i].r;
     94         if(a<=l&&r<=b) k[i].delta+=v;
     95         else if(b>=l&&a<=r)
     96         {
     97             for (R j=1;j<=k[i].siz;++j)
     98             {
     99                 id=k[i].e[j].dfs_id;
    100                 if(a<=id&&id<=b)
    101                     ad(i,j,v);
    102             }
    103         }
    104     }
    105 }
    106 
    107 ULL ask (int id,int a,int b)
    108 {
    109     ULL ans=0;
    110     int l,r,mid,lef=-1,rig=-1; //二分找到块内符合条件的部分,树状数组求和
    111     l=1,r=k[id].siz;
    112     while(l<=r)
    113     {
    114         mid=(l+r)>>1;
    115         if(k[id].e[mid].old_id>=a) lef=mid,r=mid-1;
    116         else l=mid+1;
    117     }
    118     l=1,r=k[id].siz;
    119     while(l<=r)
    120     {
    121         mid=(l+r)>>1;
    122         if(k[id].e[mid].old_id<=b) rig=mid,l=mid+1;
    123         else r=mid-1;
    124     }
    125     if(lef==-1||rig==-1) return ans;
    126     ans=query(id,rig)-query(id,lef-1);
    127     ans+=1ULL*k[id].delta*(rig-lef+1);
    128     return ans;
    129 }
    130 
    131 void build()
    132 {
    133     int cnt;
    134     bel[0]=-1;
    135     for (R i=1;i<=n;++i)
    136     {
    137         if(bel[i]!=bel[i-1]) cnt=0;
    138         k[ bel[i] ].e[++cnt].old_id=old[i];
    139         k[ bel[i] ].e[cnt].dfs_id=i;
    140     }
    141     for (R i=1;i<=block_cnt;++i)
    142         sort(k[i].e+1,k[i].e+k[i].siz+1,cmp);        
    143     for (R i=1;i<=block_cnt;++i)
    144         for (R j=1;j<=k[i].siz;++j)
    145             ad(i,j,s[ k[i].e[j].old_id ]);    
    146 }
    147 
    148 void t_add (int x,int y,ULL v)
    149 {
    150     while (Tp[x]!=Tp[y])
    151     {
    152         if(dep[ Tp[x] ]<dep[ Tp[y] ]) swap(x,y);
    153         add(id[ Tp[x] ],id[x],v);
    154         x=f[ Tp[x] ];
    155     }
    156     if(dep[x]>dep[y]) swap(x,y);
    157     add(id[x],id[y],v);
    158 }
    159 
    160 int main()
    161 {
    162     n=read(),m=read();
    163     for (R i=1;i<=n;++i) d[i]=read();
    164     for (R i=1;i<=n;++i)
    165     {
    166         a=read(),b=read();
    167         if(a==0) rt=b;
    168         else add(a,b),add(b,a);
    169     }
    170     block_siz=sqrt(n);
    171     for (R i=1;i<=n;++i) bel[i]=i/block_siz+1;
    172     block_cnt=bel[n];
    173     for (R i=1;i<=block_cnt;++i) k[i].l=n+1;
    174     for (R i=1;i<=n;++i) k[ bel[i] ].l=min(k[ bel[i] ].l,i),k[ bel[i] ].r=i; //处理每个块的左右端点
    175     for (R i=1;i<=block_cnt;++i) k[i].siz=k[i].r-k[i].l+1;
    176     dep[rt]=1;
    177     dfs1(rt);
    178     dfs2(rt,rt);
    179     build();
    180     for (R i=1;i<=m;++i)
    181     {
    182         opt=read();
    183         if(opt==1)
    184         {
    185             pos=read(),v=read();
    186             t_add(rt,pos,v-d[pos]);
    187             d[pos]=v;
    188         }
    189         else
    190         {
    191             l=read(),r=read();
    192             ans=0;
    193             for (R i=1;i<=block_cnt;++i)
    194                 ans+=ask(i,l,r);
    195             printf("%llu
    ",ans);
    196         }
    197     }
    198     return 0;
    199 }
    200 // 首先树剖,按照dfs序分块,块内按照原序号排序,
    普通计算姬

     

      Mato的文件管理:https://www.lydsy.com/JudgeOnline/problem.php?id=3289

      题意概述:区间静态逆序对。

      这是一道很适合分块的题目,更是一道适合卡常的题目,不过卡了很久也没过那道YNOI,只过了这个普通版。

      首先将序列分块,那么答案就是:整块与整块之间的逆序对数,整块与散点之间的逆序对数,散点与散点之间的逆序对数。

    ---shzr

  • 相关阅读:
    【操作系统】主存空间的分配和回收
    学术诚信与职业道德
    读《构建之法》第 8、9、10 章有感
    操作系统第三次作业
    sprint
    软件工程学期总结
    实验四 主存空间的分配和回收
    第二个冲刺
    Scrum项目6.0 和8910章读后感
    Spring 计划 7.0
  • 原文地址:https://www.cnblogs.com/shzr/p/9740376.html
Copyright © 2011-2022 走看看