分块
今天决定要学分块!
于是就先开这么一篇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 }
数列分块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 }
数列分块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 }
数列分块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