分块

  今天决定要学分块!

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

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

  

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

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

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

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

  但是这不是重点。

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

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

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

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

  $O(M\times\sqrt{N}\times logN\times log\sqrt{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\n",ans);
196         }
197     }
198     return 0;
199 }
200 // 首先树剖,按照dfs序分块,块内按照原序号排序,
普通计算姬

 

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

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

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

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

---shzr

posted @ 2018-10-03 18:18  shzr  阅读(399)  评论(0编辑  收藏  举报