分块学习笔记
分块
优雅的暴力。
分块的思想是通过划分和预处理来达到时间复杂度的平衡。
分块后任取一区间,中间会包含整块和零散块。一般对零散块暴力处理,整块通过预处理的信息计算。
常见的分块有数列分块,值域分块,数论分块等,运用于不同的情境。
分块的复杂度一般劣于线段树等
总体来说 : 划分 -> 预处理 -> 操作
数列分块#
洛谷P3374 【模板】树状数组 1#
维护一个长度为
的数列 ,共 次操作 。
1 x k
将第个数加上 。
2 x y
输出区间内每个数的和 。
。
1.划分
可以用固定块长划分,也可以直接
此时数列被划分成
注意可能最后一块是不完整的,块长不一定为
2.预处理
因为要求和,所以我们预处理出每一块的元素和。
用
3.操作
两种,一个修改一个查询。
先来看修改,将第
再看查询,设
表示
此时
-
左边的散块
这部分内元素个数不超过 ,直接统计求和。 -
中间的完整块
完整块的个数不超过 个,枚举每个块并将其 相加即可。
注意当 时中间是没有完整块的,但是并不影响。 -
右边的散块
这部分内元素个数不超过 ,直接统计求和。
这里散块有可能是完整的一块,不过不影响。
#include<cmath>
#include<cstdio>
const int M=5e5+10,len=800;
int n,m;
int L[len],R[len],bel[M];
int a[M],sum[len];
void build(){
int size=sqrt(n);//块长
for(int i=1;i<=n;i++) bel[i]=(i-1)/size+1;//计算元素所在块
for(int i=1;i<=bel[n];i++) L[i]=(i-1)*size+1,R[i]=i*size;//每一块的左右端点
R[bel[n]]=n;//最后一块的右端点为n
for(int i=1;i<=bel[n];i++)//枚举每一块
for(int j=L[i];j<=R[i];j++) sum[i]+=a[j];
}
void modify(int x,int k){//修改
a[x]+=k;
sum[bel[x]]+=k;//所在块的和也要修改
}
int query(int l,int r){
int p=bel[l],q=bel[r],ans=0;
if(p==q){
for(int i=l;i<=r;i++) ans+=a[i];
//两端点在同一块内,直接暴力统计。
return ans;
}else{
for(int i=l;i<=R[p];i++) ans+=a[i];//左边的散块
for(int i=L[q];i<=r;i++) ans+=a[i];//右边的散块
for(int i=p+1;i<=q-1;i++) ans+=sum[i];//中间的整块
return ans;
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
build();
for(int i=1,opt,x,y;i<=m;i++){
scanf("%d%d%d",&opt,&x,&y);
if(opt==1) modify(x,y);
if(opt==2) printf("%d\n",query(x,y));
}
return 0;
}
洛谷P3372 【模板】线段树 1#
维护一个长度为
的数列 ,共 次操作。
1 x y k
将区间的每个元素加上 。
2 x y
询问区间的元素和。
。
上一题的升级版。
对于操作
设
直接暴力操作即可。
这个时候
-
左边的散块
暴力操作,最多 个元素。 -
中间的完整块
对其打上懒标记 ,表示这一块整体还剩 没有加上去。 -
右边的散块
暴力操作,最多 个元素。
查询和上一题差不多,但是要记得加上
这题的懒标记下传不下传都行。这里写了下传的版本。下传时注意是散块的下传。
记得开 long long
。
void pushdown(int p){
for(int i=L[p];i<=R[p];i++) a[i]+=lazy[p];
sum[p]+=lazy[p]*(R[p]-L[p]+1);//维护块内和
lazy[p]=0;//清空懒标记
}
void modify(int l,int r,int k){
int p=bel[l],q=bel[r];
if(p==q){
pushdown(p);//散块下传懒标记
for(int i=l;i<=r;i++) a[i]+=k;
sum[p]+=(r-l+1)*k;
}else{
pushdown(p),pushdown(q);//处理左右散块
for(int i=l;i<=R[p];i++) a[i]+=k;
for(int i=L[q];i<=r;i++) a[i]+=k;
sum[p]+=(R[p]-l+1)*k;
sum[q]+=(r-L[q]+1)*k;
for(int i=p+1;i<=q-1;i++) lazy[i]+=k;//整块打上懒标记
}
}
int query(int l,int r){
int p=bel[l],q=bel[r],ans=0;
if(p==q){
pushdown(p);//其实查询操作可以不用下传,但是这里下传后代码更加简洁
for(int i=l;i<=r;i++) ans+=a[i];
}else{
pushdown(p),pushdown(q);
for(int i=l;i<=R[p];i++) ans+=a[i];//左散块的和
for(int i=L[q];i<=r;i++) ans+=a[i];//右散块的和
for(int i=p+1;i<=q-1;i++) ans+=sum[i]+lazy[i]*(R[i]-L[i]+1);
}
return ans;
}
洛谷 P2801 教主的魔法#
维护一个长度为
的数列,共 次操作 :
M l r w
区间所有元素加上 。
A l r c
询问区间有多少数大于等于 。
, 。
先看查询操作。
如果在一个单调不降的数组内查询,就可以用
但是这个数列不一定是有序的,所以可以分块后对每一块维护一个 vector
,来存储块内元素的不降序排列。
这样散块暴力统计,整块二分查找,可以做到
那么询问解决了,接下来看如何修改,分整块和散块讨论。
整块:整块同时加上一个数 vector
。
散块:部分加上一个数 vector
即可。也可以通过归并排序来实现
但是当你将块长定为
会
void restruct(int p){
v[p].clear();
for(int i=L[p];i<=R[p];i++) v[p].push_back(a[i]);
sort(v[p].begin(),v[p].end());
}
void pushdown(int p){
for(int i=L[p];i<=R[p];i++) a[i]+=lazy[p];
lazy[p]=0;
}
void build(){
int size=666;
for(int i=1;i<=n;i++) bel[i]=(i-1)/size+1;
for(int i=1;i<=bel[n];i++)
L[i]=(i-1)*size+1,R[i]=i*size;
R[bel[n]]=n;
for(int i=1;i<=bel[n];i++) restruct(i);
}
void update(int l,int r,int w){
int p=bel[l],q=bel[r];
if(p==q){
pushdown(p);
for(int i=l;i<=r;i++) a[i]+=w;
restruct(p);
}else{
pushdown(p),pushdown(q);
for(int i=l;i<=R[p];i++) a[i]+=w;
for(int i=L[q];i<=r;i++) a[i]+=w;
restruct(p),restruct(q);
for(int i=p+1;i<=q-1;i++) lazy[i]+=w;
}
}
int query(int l,int r,int c){
int p=bel[l],q=bel[r],ret=0;
if(p==q){
for(int i=l;i<=r;i++)
if(a[i]+lazy[p]<c) ret++;
}else{
for(int i=l;i<=R[p];i++)
if(a[i]+lazy[p]<c) ret++;
for(int i=L[q];i<=r;i++)
if(a[i]+lazy[q]<c) ret++;
for(int i=p+1;i<=q-1;i++)
ret+=(lower_bound(v[i].begin(),v[i].end(),c-lazy[i])-v[i].begin());
}
return (r-l+1)-ret;
}
洛谷P4168 [Violet]蒲公英#
给定一个长度为
的序列, 次查询区间 中最小的众数 。
, , , 强制在线 。
比较好想的一道题。
设
对于查询,很显然答案要么是中间整块的最小众数,要么是左右散块中的元素。
前者就是预处理的
最后前后两者取出现次数最大值即可。
int L[N],R[N],bel[M];
int pre[N][M];//值m在前n个块共有pre[n][m]个
int mode[N][N];//i~j块的众数为mode[i][j]
int buc[M];
void build(){
int size=216;
for(int i=1;i<=n;i++) bel[i]=(i-1)/size+1;
for(int i=1;i<=bel[n];i++){
L[i]=(i-1)*size+1;
R[i]=i*size;
}
R[bel[n]]=n;
for(int i=1;i<=bel[n];i++)
for(int j=L[i];j<=R[i];j++) pre[i][a[j]]++;
for(int i=1;i<=n;i++)
for(int j=1;j<=bel[n];j++) pre[j][i]+=pre[j-1][i];
for(int i=1;i<=bel[n];i++){
memset(buc,0,sizeof buc);
for(int j=i;j<=bel[n];j++){
mode[i][j]=mode[i][j-1];
for(int k=L[j];k<=R[j];k++){
buc[a[k]]++;
if((buc[a[k]]>buc[mode[i][j]])
or (buc[a[k]]==buc[mode[i][j]] and a[k]<mode[i][j])) mode[i][j]=a[k];
}
}
}
memset(buc,0,sizeof buc);
}
void clear(int l,int r){
int p=bel[l],q=bel[r];
if(p==q){
for(int i=l;i<=r;i++) buc[a[i]]=0;
}else{
for(int i=l;i<=R[p];i++) buc[a[i]]=0;
for(int j=L[q];j<=r;j++) buc[a[j]]=0;
}
}
int query(int l,int r){
int p=bel[l],q=bel[r];
int ans=0,tot=0;
if(q-p<=1){
for(int i=l;i<=r;i++) buc[a[i]]++;
for(int i=l;i<=r;i++)
if(buc[a[i]]>tot or (buc[a[i]]==tot and a[i]<ans)) ans=a[i],tot=buc[a[i]];
}else{
for(int i=l;i<=R[p];i++){
buc[a[i]]++;
int tmp=pre[q-1][a[i]]-pre[p][a[i]]+buc[a[i]];
if(tmp>tot or (tmp==tot and a[i]<ans)) ans=a[i],tot=tmp;
}
for(int i=L[q];i<=r;i++){
buc[a[i]]++;
int tmp=pre[q-1][a[i]]-pre[p][a[i]]+buc[a[i]];
if(tmp>tot or (tmp==tot and a[i]<ans)) ans=a[i],tot=tmp;
}
int hzx=mode[p+1][q-1],tmp=pre[q-1][hzx]-pre[p][hzx]+buc[hzx];
if(tmp>tot or (tmp==tot and hzx<ans)) ans=hzx,tot=tmp;
}
clear(l,r);
return ans;
}
洛谷 P8576 「DTOI-2」星之界#
长度为
的序列 , 次操作。
1 l r x y
将中所有值 改为 。
2 l r
输出的值。
, 。
利用并查集打懒标记。
先看查询操作。
这么复杂的柿子不好维护,所以考虑将其化简。
所以需要维护块内的元素和
然后再看修改操作。显然一个个改会T飞,所以考虑能快速合并两个值的工具。
若将块内所有值为
并查集中每个集合有一个父亲。所以设在第
当块
对出现次数
然后分类讨论:
未出现在这一块,即
对这一块无影响,直接跳过即可。 有出现,而 未出现,即 。
直接 , 即可。
表示 第一次出现的位置变成了原来 第一次出现的位置, 所代表的数变成了 。 均有出现
并查集内将 指向 ,这样找 的代表元时最终会找到 的代表元,即 被修改成了 。
再考虑对于
那么
散块需要下传标记。a[i] = rtv[find(i)]
,就可以还原
还有预处理,要计算出
这题卡空间,所以用 int
。
还有个细节,修改操作中
//dsu 为并查集
int frc[N],inv[N];//inv[x] = (x!)^(-1)
int frcpow[M][len];//frcpow[x][p] = (x!)^p
int invpow[M][len];//invpow[x][p] = (x!)^(-p)
void calc(){
frc[1]=inv[1]=1;
for(int i=2;i<N;i++) frc[i]=1ll*frc[i-1]*i%mod;
for(int i=2;i<N;i++) inv[i]=1ll*inv[mod%i]*(mod-mod/i)%mod;
for(int i=2;i<N;i++) inv[i]=1ll*inv[i]*inv[i-1]%mod;
for(int i=1;i<M;i++){
frcpow[i][0]=invpow[i][0]=1;
for(int j=1;j<len;j++){
frcpow[i][j]=1ll*frcpow[i][j-1]*frc[i]%mod;
invpow[i][j]=1ll*invpow[i][j-1]*inv[i]%mod;
}
}
}
int L[len],R[len],bel[M];
int sum[len],frm[len];//sum : 区间和 frm : 区间阶乘逆元积
int fir[len][M],rtv[M],cnt[len][M];//fir : 代表元 rtv : 代表元所代表的数 cnt : 块内计数
void restruct(int p){
frm[p]=1,sum[p]=0;
for(int i=L[p];i<=R[p];i++){
if(!fir[p][a[i]]){
dsu.fa[i]=fir[p][a[i]]=i;
cnt[p][a[i]]=1;
rtv[i]=a[i];
}else{
dsu.fa[i]=fir[p][a[i]];
cnt[p][a[i]]++;
}
sum[p]+=a[i],frm[p]=1ll*frm[p]*inv[a[i]]%mod;
}
}
void pushdown(int p){
for(int i=L[p];i<=R[p];i++){
a[i]=rtv[dsu.find(i)];
fir[p][a[i]]=cnt[p][a[i]]=0;
}
for(int i=L[p];i<=R[p];i++) dsu.fa[i]=0;//这个要放在外面
}
void build(){
int size=sqrt(n);
for(int i=1;i<=n;i++) bel[i]=(i-1)/size+1;
for(int i=1;i<=bel[n];i++) L[i]=(i-1)*size+1,R[i]=i*size;
R[bel[n]]=n;
for(int i=1;i<=bel[n];i++) restruct(i);
}
void modify(int l,int r,int x,int y){
int p=bel[l],q=bel[r];
if(p==q){
pushdown(p);
for(int i=l;i<=r;i++)
if(a[i]==x) a[i]=y;
restruct(p);
}else{
pushdown(p),pushdown(q);
for(int i=l;i<=R[p];i++) if(a[i]==x) a[i]=y;
for(int i=L[q];i<=r;i++) if(a[i]==x) a[i]=y;
restruct(p),restruct(q);
for(int i=p+1;i<=q-1;i++){
int cx=cnt[i][x];
//if(!cx) continue;
sum[i]+=cx*(y-x);
frm[i]=1ll*frm[i]*frcpow[x][cx]%mod*invpow[y][cx]%mod;//维护分母的逆元
cnt[i][y]+=cnt[i][x];
if(!fir[i][y]) fir[i][y]=fir[i][x],rtv[fir[i][y]]=y;
else dsu.fa[fir[i][x]]=fir[i][y];
fir[i][x]=cnt[i][x]=0;
}
}
}
int query(int l,int r){
int p=bel[l],q=bel[r];
int tmpsum=0,tmpmul=1;
if(p==q){
for(int i=l;i<=r;i++){
int cur=rtv[dsu.find(i)];
tmpsum+=cur,tmpmul=1ll*tmpmul*inv[cur]%mod;
}
}else{
for(int i=l;i<=R[p];i++){
int cur=rtv[dsu.find(i)];
tmpsum+=cur,tmpmul=1ll*tmpmul*inv[cur]%mod;
}
for(int i=L[q];i<=r;i++){
int cur=rtv[dsu.find(i)];
tmpsum+=cur,tmpmul=1ll*tmpmul*inv[cur]%mod;
}
for(int i=p+1;i<=q-1;i++){
tmpsum+=sum[i];
tmpmul=1ll*tmpmul*frm[i]%mod;
}
}
return 1ll*frc[tmpsum]*tmpmul%mod;
}
CodeForces 896E Welcome home, Chtholly#
维护一个长度为
的序列 ,共 次操作 :
1 l r x
将中所有大于 的元素减去 。
2 l r x
询问中多少个元素等于 。
第二分块,比较简单的。又叫瑟尼欧里斯树 (?)。
先看修改操作。暴力做法是直接将值域
如何判断平移哪一段区间?设
这一块内不需要任何操作。
那么就将 值域平移到 ,这样在 的时间内将值域减小了 。
那么就将 值域平移到 ,这样在 的时间内将值域减小了 。
这样就保证了每块的值域都被执行了
然后还需要个快速合并相同值的数据结构,也就是上一题用到的
注意
在操作时也要注意
struct DSU{
int fa[M];
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
void merge(int x,int y){return fa[find(x)]=find(y),void();}
bool query(int x,int y){return find(x)==find(y);}
}dsu; //并查集
int L[len],R[len],bel[M],lazy[len],maxn[len];
int fir[len][M],cnt[len][M],rtv[M];
void restruct(int p){
maxn[p]=0;
for(int i=L[p];i<=R[p];i++){
cnt[p][a[i]]++;
if(!fir[p][a[i]]){
fir[p][a[i]]=dsu.fa[i]=i;
rtv[i]=a[i];
}else{
dsu.fa[i]=fir[p][a[i]];
}
maxn[p]=max(maxn[p],a[i]);
}
}
void pushdown(int p){
for(int i=L[p];i<=R[p];i++){
a[i]=rtv[dsu.find(i)];
fir[p][a[i]]=cnt[p][a[i]]=0;
a[i]-=lazy[p]; //注意这里要减去 lazy ,而上一行不用
}
for(int i=L[p];i<=R[p];i++) dsu.fa[i]=0;
lazy[p]=0;
}
void build(){
int size=sqrt(n);
for(int i=1;i<=n;i++) bel[i]=(i-1)/size+1;
for(int i=1;i<=bel[n];i++) L[i]=(i-1)*size+1,R[i]=i*size;
R[bel[n]]=n;
for(int i=1;i<=bel[n];i++) restruct(i);
}
void merge(int p,int x,int y){ // 第 p 块中的 x -> y
if(!fir[p][x]) return;
cnt[p][y]+=cnt[p][x];
if(!fir[p][y]) fir[p][y]=fir[p][x],rtv[fir[p][x]]=y;
else dsu.fa[fir[p][x]]=fir[p][y];
fir[p][x]=cnt[p][x]=0;
}
void modify(int l,int r,int x){
int p=bel[l],q=bel[r];
if(p==q){
pushdown(p);
for(int i=l;i<=r;i++) if(a[i]>x) a[i]-=x;
restruct(p);
}else{
pushdown(p),pushdown(q);
for(int i=l;i<=R[p];i++) if(a[i]>x) a[i]-=x;
for(int i=L[q];i<=r;i++) if(a[i]>x) a[i]-=x;
restruct(p),restruct(q);
for(int i=p+1;i<=q-1;i++){
if(x>=maxn[i]-lazy[i]) continue;//真实的最大值是 maxn - lazy
if(x*2<=maxn[i]-lazy[i]){
for(int j=lazy[i]+1;j<=lazy[i]+x;j++) merge(i,j,j+x);
lazy[i]+=x;
}else{
for(int j=lazy[i]+x+1;j<=maxn[i];j++) merge(i,j,j-x);
maxn[i]=min(maxn[i],lazy[i]+x);
//原本修改后区间最大值为 min{x , maxn[i]}
//但是有懒标记,所以最大值为 min{lazy[i]+x , maxn[i]}
}
}
}
}
int query(int l,int r,int x){
int p=bel[l],q=bel[r],ans=0;
if(p==q){
for(int i=l;i<=r;i++)
if(rtv[dsu.find(i)]-lazy[p]==x) ans++;
}else{
for(int i=l;i<=R[p];i++)
if(rtv[dsu.find(i)]-lazy[p]==x) ans++;
for(int i=L[q];i<=r;i++)
if(rtv[dsu.find(i)]-lazy[q]==x) ans++;
for(int i=p+1;i<=q-1;i++)
if(lazy[i]+x<M) ans+=cnt[i][lazy[i]+x];
}
return ans;
}
值域分块#
和数列分块类似,但是分的是值域。值域较小的时候可以使用。
有时候其实就是数列分块,此时数列是一个桶。
比较常用于根号平衡等。一般是辅助工具。
洛谷 P1138 第 k 小整数#
现有
个正整数,求出其中的第 小整数,相同的数算一次。无解输出 NO RESULT
。
, , 。
值域很小所以可以开个桶,然后再对桶分块,块长约为
然后看询问操作。先考虑如何仅用桶查询区间第
但是这样复杂度是
所以一块块加起来,大于等于
const int MAXN=30000;//值域
int L[len],R[len],bel[M];
int sum[len],cnt[M];
//sum 是块内和,cnt 是桶
void build(){
int size=sqrt(MAXN); //值域分块,总长为值域
for(int i=1;i<=MAXN;i++) bel[i]=(i-1)/size+1;
for(int i=1;i<=bel[MAXN];i++) L[i]=(i-1)*size+1,R[i]=i*size;
R[bel[MAXN]]=MAXN;
for(int i=1;i<=n;i++)
if(!cnt[a[i]]) cnt[a[i]]++,sum[bel[a[i]]]++;
//这里要注意去重
}
int query(int k){//总体第 k 小
int cur=0,tot=0;
for(int i=1;i<=bel[MAXN];i++) //找到相应的块
if(tot+sum[i]>=k){cur=i;break;}
else tot+=sum[i];
for(int i=L[cur];i<=R[cur];i++) //块内找到相应的数
if(tot+cnt[i]>=k) return i;
else tot+=cnt[i];
return -1;
}
洛谷 P4119 [Ynoi2018] 未来日记#
给定一个长度为
的序列 , 次操作。
1 l r x y
将区间中的 改为 。
2 l r k
查询区间的第 小值。
最初分块。
这个修改操作显然是序列分块 + 并查集。
对于查询操作,发现可以值域分块。与上一题不同的是从全局
所以对值域进行分块,维护两个数组 :
对于查询的散块,单独开两个数组,
当查询
则
然后就可以像上题一样查询了。查询完之后要清空
这时候考虑修改操作对
因为这题的修改操作,每次最多多出一种数,所以数字个数是
const int MAXN=100000; //值域
struct DSU{
int fa[M];
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
}dsu;
int L[len],R[len],bel[M];//序列分块
int Zl[M],Zr[M],Zbel[M],Zpre[len][len],Zcnt[len][M];//值域分块
//Zpre[i][j] 前 i 块中值域在第 j 块的元素个数
//Zcnt[i][v] 前 i 块中值为 v 的元素个数
int fir[len][M],cnt[len][M],rtv[M];
inline void restruct(int p){
for(register int i=L[p];i<=R[p];i++){
cnt[p][a[i]]++;
if(!fir[p][a[i]]){
fir[p][a[i]]=dsu.fa[i]=i;
rtv[i]=a[i];
}else{
dsu.fa[i]=fir[p][a[i]];
}
}
}
inline void pushdown(int p){
for(register int i=L[p];i<=R[p];i++){
a[i]=rtv[dsu.find(i)];
fir[p][a[i]]=cnt[p][a[i]]=0;
}
for(register int i=L[p];i<=R[p];i++) dsu.fa[i]=0;
}
inline void build(){
int size=599;
for(register int i=1;i<=n;i++) bel[i]=(i-1)/size+1;
for(register int i=1;i<=MAXN;i++) Zbel[i]=(i-1)/size+1;
for(register int i=1;i<=bel[n];i++) L[i]=(i-1)*size+1,R[i]=i*size;
for(register int i=1;i<=Zbel[MAXN];i++) Zl[i]=(i-1)*size+1,Zr[i]=i*size;
R[bel[n]]=n,Zr[bel[MAXN]]=MAXN;
for(register int i=1;i<=bel[n];i++){
restruct(i);
for(register int j=1;j<=Zbel[MAXN];j++) Zpre[i][j]=Zpre[i-1][j];
for(register int j=1;j<=MAXN;j++) Zcnt[i][j]=Zcnt[i-1][j];
for(register int j=L[i];j<=R[i];j++)
Zpre[i][Zbel[a[j]]]++,Zcnt[i][a[j]]++;
}
}
inline void merge(int p,int x,int y){
if(!fir[p][x]) return;
cnt[p][y]+=cnt[p][x];
if(!fir[p][y]) fir[p][y]=fir[p][x],rtv[fir[p][x]]=y;
else dsu.fa[fir[p][x]]=fir[p][y];
fir[p][x]=cnt[p][x]=0;
}
inline void modify(int l,int r,int x,int y){
if(x==y) return;
int p=bel[l],q=bel[r];
if(p==q){
pushdown(p);
int cntx=0;
for(register int i=l;i<=r;i++)
if(a[i]==x) a[i]=y,cntx++;
for(register int i=p;i<=bel[n];i++){
Zpre[i][Zbel[x]]-=cntx,Zcnt[i][x]-=cntx;
Zpre[i][Zbel[y]]+=cntx,Zcnt[i][y]+=cntx;
}
restruct(p);
}else{
int cntx=0;//x 的个数 的前缀和
pushdown(p);
for(register int i=l;i<=R[p];i++)
if(a[i]==x) cntx++,a[i]=y;
restruct(p);
Zpre[p][Zbel[x]]-=cntx,Zcnt[p][x]-=cntx;
Zpre[p][Zbel[y]]+=cntx,Zcnt[p][y]+=cntx;
for(register int i=p+1;i<=q-1;i++){
cntx+=cnt[i][x];
Zpre[i][Zbel[x]]-=cntx,Zcnt[i][x]-=cntx;
Zpre[i][Zbel[y]]+=cntx,Zcnt[i][y]+=cntx;
merge(i,x,y);
}
pushdown(q);
for(register int i=L[q];i<=r;i++)
if(a[i]==x) cntx++,a[i]=y;
restruct(q);
Zpre[q][Zbel[x]]-=cntx,Zcnt[q][x]-=cntx;
Zpre[q][Zbel[y]]+=cntx,Zcnt[q][y]+=cntx;
for(register int i=q+1;i<=bel[n];i++){
Zpre[i][Zbel[x]]-=cntx,Zcnt[i][x]-=cntx;
Zpre[i][Zbel[y]]+=cntx,Zcnt[i][y]+=cntx;
}
}
}
int Zp1[len],Zp2[M];
inline int query(int l,int r,int k){
int p=bel[l],q=bel[r],ans=0;
if(p==q){
for(register int i=l;i<=r;i++)
Zp1[Zbel[rtv[dsu.find(i)]]]++,Zp2[rtv[dsu.find(i)]]++;
int tot=0,cur=0;
for(register int i=1;i<=Zbel[MAXN];i++)
if(tot+Zp1[i]>=k){cur=i;break;}
else tot+=Zp1[i];
for(register int i=Zl[cur];i<=Zr[cur];i++)
if(tot+Zp2[i]>=k){ans=i;break;}
else tot+=Zp2[i];
for(register int i=l;i<=r;i++)
Zp1[Zbel[rtv[dsu.find(i)]]]=Zp2[rtv[dsu.find(i)]]=0;
}else{
for(register int i=l;i<=R[p];i++)
Zp1[Zbel[rtv[dsu.find(i)]]]++,Zp2[rtv[dsu.find(i)]]++;
for(register int i=L[q];i<=r;i++)
Zp1[Zbel[rtv[dsu.find(i)]]]++,Zp2[rtv[dsu.find(i)]]++;
int tot=0,cur=0;
for(register int i=1;i<=Zbel[MAXN];i++)
if(tot+Zp1[i]+Zpre[q-1][i]-Zpre[p][i]>=k){cur=i;break;}
else tot+=Zp1[i]+Zpre[q-1][i]-Zpre[p][i];
for(register int i=Zl[cur];i<=Zr[cur];i++)
if(tot+Zp2[i]+Zcnt[q-1][i]-Zcnt[p][i]>=k){ans=i;break;}
else tot+=Zp2[i]+Zcnt[q-1][i]-Zcnt[p][i];
for(register int i=l;i<=R[p];i++)
Zp1[Zbel[rtv[dsu.find(i)]]]=Zp2[rtv[dsu.find(i)]]=0;
for(register int i=L[q];i<=r;i++)
Zp1[Zbel[rtv[dsu.find(i)]]]=Zp2[rtv[dsu.find(i)]]=0;
}
return ans;
}
但是这题改成
考虑到每次重构都要将散块的并查集全部推倒后重建。但是修改操作只对两种值有影响,所以可以只修改关于值
具体的,用一个栈记录下散块中值为
int stk[M],top;
void update(int p,int l,int r,int x,int y){
top=0; int tot=0;
fir[p][x]=fir[p][y]=0;
for(int i=L[p];i<=R[p];i++){
a[i]=a[dsu.find(i)];
if(a[i]==x or a[i]==y) stk[++top]=i;
}
for(int i=l;i<=r;i++)
if(a[i]==x) a[i]=y,tot++;
for(int i=1;i<=top;i++){
if(!fir[p][y]) fir[p][y]=dsu.fa[stk[i]]=stk[i];
else dsu.fa[stk[i]]=fir[p][y];
}
cnt[p][x]-=tot,cnt[p][y]+=tot;
for(int i=p;i<=bel[n];i++){
Zcnt[i][x]-=tot,Zcnt[i][y]+=tot;
Zpre[i][Zbel[x]]-=tot,Zpre[i][Zbel[y]]+=tot;
}
}
void modify(int l,int r,int x,int y){
if(x==y) return;
int p=bel[l],q=bel[r];
if(p==q){
update(p,l,r,x,y);
}else{
update(p,l,R[p],x,y),update(q,L[q],r,x,y);
int sum=0;
for(int i=p+1;i<=q-1;i++){
sum+=cnt[i][x];
Zpre[i][Zbel[x]]-=sum,Zpre[i][Zbel[y]]+=sum;
Zcnt[i][x]-=sum,Zcnt[i][y]+=sum;
merge(i,x,y);
}
for(int i=q;i<=bel[n];i++){
Zpre[i][Zbel[x]]-=sum,Zpre[i][Zbel[y]]+=sum;
Zcnt[i][x]-=sum,Zcnt[i][y]+=sum;
}
}
}
询问分块#
对询问进行分块。一般在离线莫队里比较常用,但是也可以单独使用。
CF342E Xenia and Tree#
给定一棵
个节点的树,一开始除了节点 是红色,其他节点都是黑色。支持两种操作:
1 x
将点染成红色。
2 x
查询距离节点最近的红点的距离。
正解是点分树。
考虑两种暴力:
1.把所有红色点丢到队列里面进行 bfs 得到所有点答案。
2.枚举所有红点,求其到某个节点
然后通过分块把这两种操作串在一起。将询问分成
#include<cmath>
#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
const int M=1e5+10;
int n,q,D;
vector<int>G[M],qwq;
#define add(u,v) G[u].push_back(v)
int fa[M],son[M],siz[M],dep[M],top[M];
void dfs1(int u,int f){
fa[u]=f,siz[u]=1,dep[u]=dep[f]+1;
for(int v:G[u])
if(v!=f) dfs1(v,u),siz[u]+=siz[v],
(siz[v]>siz[son[u]])?(son[u]=v):0;
}
void dfs2(int u,int t){
top[u]=t;
if(!son[u]) return; dfs2(son[u],t);
for(int v:G[u])
if(v!=fa[u] && v!=son[u]) dfs2(v,v);
}
int LCA(int u,int v){
while(top[u]!=top[v])
dep[top[u]]<dep[top[v]]?(v=fa[top[v]]):(u=fa[top[u]]);
return dep[u]<dep[v]?u:v;
}
int dist(int u,int v){
return dep[u]+dep[v]-2*dep[LCA(u,v)];
}
bool red[M]; int ans[M];
void restruct(){
queue<int>q;
for(int i=1;i<=n;i++) red[i]?(q.push(i),ans[i]=0):(ans[i]=-1);
while(!q.empty()){
int u=q.front(); q.pop();
for(int v:G[u])
if(ans[v]==-1) ans[v]=ans[u]+1,q.push(v);
}
}
int main(){
scanf("%d%d",&n,&q);
for(int i=1,u,v;i<n;i++)
scanf("%d%d",&u,&v),add(u,v),add(v,u);
dfs1(1,0),dfs2(1,1);
red[1]=true,D=sqrt(q);
restruct();
for(int i=1,opt,u;i<=q;i++){
scanf("%d%d",&opt,&u);
if(opt==1) qwq.push_back(u);
if(opt==2){
int res=ans[u];
for(int v:qwq) res=min(res,dist(u,v));
printf("%d\n",res);
}
if(i%D==0){
for(int v:qwq) red[v]=true;
restruct(),qwq.clear();
}
}
return 0;
}
作者:zzxLLL
出处:https://www.cnblogs.com/zzxLLL/p/17071636.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】