关于根号分治

根号分治就是暴力+暴力!——recloud

定义

根号分治是一种写题的trick(技巧),只是一种思想,以例题来作讲解

1.CF1207F

image

区间查询,单点修改。

很容易有两种很直接的暴力思路:

  • 单点直接暴力修改:\(O(1)\)+区间暴力查询\(O( \dfrac{5e5}{x} )\)
    当x每次为1,总体时间可达\(O(5e5*n)=O(n^2)\)

  • 修改时维护一个\(ans[i][j],\)表示取模\(x\)\(y\)的总值,每次暴力遍历x,当x为n时,可达\(O(x)=O(n)\)
    查询时直接ans输出,\(O(1)\),总体时间为\(O(n^2)\).

欸,两个\(n^2\)image

观察一下:,第一个式子的难点在于\(O( \dfrac{5e5}{x} )\),当x过小,查询过复杂

第二个式子在于修改时\(O(x)\)x太大修改太复杂,结合一下?

我们预订一个阈值\(b\),\(x\)大于\(b\)时,使用方案一,否则使用方案二

代码如下,本题阈值为700:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M=5e5+110,N=1e3+110;
inline int read(){
	int sum=0,k=1;char c=getchar();
	while(c>'9'||c<'0'){if(c=='-')k=-1;c=getchar();
	}while(c>='0'&&c<='9'){sum=sum*10+c-48;c=getchar();
	}return sum*k;
}
inline void wr(int x){
	if(x<0) putchar('-'),x=-x;
	if(x>9) wr(x/10);
	putchar(x%10+'0');
}
inline void co(int x){
	wr(x);putchar('\n');
}
int q=0,ans=0;
int a[M],res[N][N];

signed main(){
	q=read();
	while(q--){
		int opt=read(),x=read(),y=read();
		if(opt==1){
			for(int i=1;i<700;i++) res[i][x%i]+=y;
			a[x]+=y;
		}else{
			if(x<700) co(res[x][y]);
			else{
				for(int i=y;i<=M-110;i+=x) ans+=a[i];
				co(ans);ans=0;	
			} 
		}
	}
	return 0;
}

至于为什么阈值为700?

额,根号分治,5e5=500000的范围,\(\sqrt{500000}=707.1067811865476,\)而且:

image

再补一道例题吧
2. P7828 [CCO 2021] Swap Swap Sort
首先读懂题:给你一个排序权重和一个初始序列,每次交换相邻两个权重,问此时有多少逆序对
你发现每次只交换相邻两个数的权重,我们记两个数 \(x,y\)\(x\)\(y\) 前面的数量为 \((x,y)\)
那么每次的交换可以看成的\(lastans+(x,y)-(y,x)\)
你发现 逆序对+顺序对=总对数 即记值 \(x\) 出现为\(cnt_x\)
$\therefore cnt_x \times cnt_y=(x,y)+(y,x) $
而记下出现次数是可以 \(O(1)\) 维护的
所以我们可以把式子转化:

\[ans=lastans+(cnt_x\times cnt_y)-2(y,x) \]

  • 所以瓶颈在于维护\((y,x)\)
    发现值域很小所以考虑对数字\(x\)的出现次数\(cnt_x\)进行根号分治
    我们设阈值为 \(S\)
    \(cnt_x\) 小于 \(S\) 时我们记一个 \(vector\)\(x\) 出现的每一个下标,两个都小于 \(S\) 时直接用双指针扫一遍 , 这么做是 \(O(q \times S)\) 的,因为出现次数不超过 \(S\)
    \(cnt_x\) 大于 \(S\) 时,我们直接枚举后预处理出来对每种颜色的贡献,那么这种复杂度是 \(O(n\times \frac{n}{S})\)
    这么做的总复杂度是\(O(q \times S+n\times \frac{n}{S})\)
    问题在于 \(S\) 的取值, 均值不等式
    \(\sqrt{a*b}\le \frac{a+b}{2}\)\(a=b\) 时取等,即\(q\times S=n \times \frac{n}{S}\)

\[S^2=\frac{n^2}{q} \to S=\sqrt{\frac{n^2}{q}}\to n\le 1e5,q\le 1e6\to S=100 \]

  • 所以理论当\(S\)取100时取得最优复杂度,实测有波动
    还有就是对最开始还要求一遍初始逆序对,我用的权值线段树)
    以下是代码
/*
雲璃猫猫が好きです
すべての生命よ,歌のように輝いています
截剣式、斬、断、破です!
*/
#include<bits/stdc++.h>
#include<bits/extc++.h>
#define int long long
#define INF 1e18
#define lb long double
#define ls (id<<1)
#define rs (id<<1|1)
#define rep(i,l,r,k) for(int i=(l);i<=(r);i+=(k))
#define dep(i,r,l,k) for(int i=(r);i>=(l);i-=(k))
#define tep(x,y) for(auto x:y)
#define wl while
#define mk(a,b) make_pair(a,b)
#define me(a,b) memset(a,b,sizeof(a))
#define pb(x) push_back(x)
#define pr putchar
#define fi first
#define se second
#define max(a,b)((a)>(b)?(a):(b))
#define min(a,b)((a)<(b)?(a):(b))
using namespace std;
random_device rd;
unsigned int seed=rd();
mt19937 Rand(seed);
typedef pair<int,int> pii;
const int M=1e5+110,mod=1e9+7,Mod=998244353;
__gnu_pbds::gp_hash_table<string,int>ml;
inline int read(){int sum=0,k=1;char c=getchar();
	while(c>'9'||c<'0'){if(c=='-')k=-1;c=getchar();
	}while(c>='0'&&c<='9'){sum=sum*10+c-48;c=getchar();
	}return sum*k;
}inline void wr(int x){if(x<0) putchar('-'),x=-x;
	if(x>9) wr(x/10);return void(putchar(x%10+'0'));}
int n,k,q,pl[M];
long long res;
int sum[M<<2];
inline void add(int id,int l,int r,int pos){
	sum[id]++;
	if(l==r) return void();
	if(l>=r) return void();
	int Mid=(l+r)>>1;
	if(Mid>=pos) add(ls,l,Mid,pos);
	else add(rs,Mid+1,r,pos);
}
inline int ask(int id,int l,int r,int L,int R){
	if(L>R) return 0;
	if(l>=L&&r<=R) return sum[id];
	if(sum[id]==0) return 0;
	int Mid=(l+r)>>1,res=0;
	if(Mid>=L) res+=ask(ls,l,Mid,L,R);
	if(Mid<R) res+=ask(rs,Mid+1,r,L,R);
	return res;
}
vector<int>pos[M];int a[M],id[M],tot,lt[M];
long long cnt[M],f[510][M];
inline int work(int l,int r){//l在r前面
	if(cnt[l]==0||cnt[r]==0) return 0;
	int res=0,p=-1;
	for(auto ps:pos[l]){
		while(p+1<cnt[r]&&pos[r][p+1]<ps) p++;
		res=res+(p+1);
	}return res;
}
signed main(){
	n=read(),k=read(),q=read();
	rep(i,1,k,1) pl[i]=i;
	rep(i,1,n,1){
		a[i]=read();
		cnt[a[i]]++;
		pos[a[i]].pb(i);
		res+=ask(1,1,k,a[i]+1,k);
		add(1,1,k,a[i]);
	}
	int LIM=250;
	rep(i,1,k,1)
		if(cnt[i]>=LIM&&cnt[i]!=0){
			id[i]=++tot;
			lt[n+1]=0;
			dep(k,n,1,1) lt[k]=lt[k+1]+(a[k]==i);
			rep(k,1,n,1) f[tot][a[k]]+=lt[k];
		}
	wl(q--){
		int b=read(),l=pl[b],r=pl[b+1];
		if(id[l]){
			res=res+(cnt[l]*cnt[r])-(2*f[id[l]][r]);
		}else if(id[r]){
			res=res-(cnt[l]*cnt[r])+(2*f[id[r]][l]);
		}else{
			res=res+(cnt[l]*cnt[r])-(2*work(l,r));
		}wr(res),pr(10);std::swap(pl[b],pl[b+1]);
	}
	return 0;
}
/*

begin :14 42
finish:15 48
end:16 30

*/
/*

*/
  1. P5355 [Ynoi Easy Round 2017] 由乃的玉米田
    考虑除法不好维护,分成两个部分
    对于x>\(\sqrt{n}\)的玩意,最多只会有\(\log{n}\)级别的因数,莫队区间直接枚举即可
    对于\(x\le \sqrt{n}\)的情况,再次离线下来预处理,枚举每个x,遍历一遍序列找到最近的对应答案位置,然后对这个值再分块作区间最大值查询
    由于预处理\(V\sqrt{n}\)每次分块区间查最大值\(m\sqrt{n}\)\(n,m,V\)同阶,所以复杂度还是\(n\sqrt{n}\)
    代码:
/*
雲璃猫猫が好きです
すべての生命よ,歌のように輝いています
截剣式、斬、断、破です!
*/
#include<bits/stdc++.h>
#include<bits/extc++.h>
#define INF 1e18
#define lb long double
#define ls (id<<1)
#define rs (id<<1|1)
#define rep(i,l,r,k) for(int i=(l);i<=(r);i+=(k))
#define dep(i,r,l,k) for(int i=(r);i>=(l);i-=(k))
#define tep(ch,cr) for(auto ch:cr)
#define mk(a,b) make_pair(a,b)
#define me(a,b) memset(a,b,sizeof(a))
#define pb(x) push_back(x)
#define pr putchar
#define fi first
#define se second
#define wl while
#define max(a,b)((a)>(b)?(a):(b))
#define min(a,b)((a)<(b)?(a):(b))
using namespace std;
random_device rd;
unsigned int seed=rd();
mt19937 Rand(seed);
typedef pair<int,int> pii;
__gnu_pbds::gp_hash_table<string,int>ml;
inline int read(){int sum=0,k=1;char c=getchar();
	while(c>'9'||c<'0'){if(c=='-')k=-1;c=getchar();
	}while(c>='0'&&c<='9'){sum=sum*10+c-48;c=getchar();
	}return sum*k;
}inline void wr(int x){if(x<0) putchar('-'),x=-x;
	if(x>9) wr(x/10);return void(putchar(x%10+'0'));}

const int M=1e5+1,C=1e5+1,N=318;
int n,m,a[M],L=1,R=0;
bitset<M> A,B,ans,LOL;
struct Node{int opt,x,l,r,id;}query[M];
int bl,pos[M],limit=N-1;
int qwq[N][M],las[M];
int fk[N][700],pp[M],qe=350;

inline void buid(){
	rep(x,1,limit,1){
		if(!LOL[x]) continue;
		me(las,0);
		rep(i,1,n,1){
			int y=a[i];
			// 先计算qwq,再更新las
			int pos1=0,pos2=0;
			if(y%x==0) pos1=las[y/x];
			if(1ll*x*y<M) pos2=las[x*y];
			qwq[x][i]=max(pos1,pos2);
			
			las[y]=i;  // 更新las放在后面
			fk[x][pp[i]]=max(fk[x][pp[i]],qwq[x][i]);
		}
	}
}

int c1[M];
inline void ad(int x){
	c1[x]++;
	if(c1[x]==1) A[x]=1,B[C-x]=1;
}
inline void dl(int x){
	c1[x]--;
	if(c1[x]==0) A[x]=0,B[C-x]=0;
}

signed main(){
	n=read(),m=read();
	bl=sqrt(n);
	rep(i,1,n,1){
		a[i]=read();
		pos[i]=(i-1)/bl+1;
		pp[i]=(i-1)/qe;
	}
	
	rep(i,1,m,1){
		int opt=read(),l=read(),r=read(),x=read();
		query[i]={opt,x,l,r,i};
		if(opt==4&&x<=limit) LOL[x]=1;
	}
	buid();
	
	sort(query+1,query+m+1,[&](Node X,Node Y){return pos[X.l]!=pos[Y.l]?pos[X.l]<pos[Y.l]:X.r<Y.r;});
	
	rep(i,1,m,1){
		int l=query[i].l,r=query[i].r,opt=query[i].opt,x=query[i].x,id=query[i].id;
		while(L<l) dl(a[L++]);
		while(L>l) ad(a[--L]);
		while(R<r) ad(a[++R]);
		while(R>r) dl(a[R--]);
		if(opt==1){
			if(x==0) ans[id]=A.any();
			else if((A&(A<<x)).any()) ans[id]=1;
		}
		else if(opt==2){
			if((A&(B>>(C-x))).any()) ans[id]=1;
		}
		else if(opt==3){
			for(int c=1;c*c<=x;c++) if(x%c==0)
				if(c<M&&x/c<M&&A[c]&&A[x/c]){
					ans[id]=1;
					break;
				}
		}
		else if(opt==4){
			if(x==0) ans[id]=0;
			else if(x==1) ans[id]=1;
			else if(x>limit){
				for(int c=1;c*x<M;c++){
					if(A[c]&&A[c*x]){
						ans[id]=1;
						break;
					}
				}
			}else{
				int ql=pp[l],qr=pp[r],maxx=0;
				if(ql==qr){
					rep(f,l,r,1) maxx=max(maxx,qwq[x][f]);
				}else{
					// 左残块
					rep(f,l,min((ql+1)*qe,n),1) maxx=max(maxx,qwq[x][f]);
					// 右残块  
					rep(f,max(1,qr*qe+1),r,1) maxx=max(maxx,qwq[x][f]);
					// 整块
					rep(f,ql+1,qr-1,1) maxx=max(maxx,fk[x][f]);
				}
				if(maxx>=l) ans[id]=1;
			}
		}
	}
	
	rep(i,1,m,1) puts(ans[i]?"yuno":"yumi");
	return 0;
}
  1. P9212 「蓬莱人形」
    根号分治题
    首先在值域上考虑限制的问题,对于形如 \(((a_i+x)\mod m)<((a_i+y)\mod m)\) 的式子。
    分类讨论 \(x\)\(y\) 的大小关系以判断答案。
  • \(x=y\)
    无论如何左右的值相等,答案一定为 \(0\)
  • \(x<y\)
    对于式子的影响即 \(a_i\) 需要满足要么左右都取模,要么左右都不取模。
    所以有形如 \(a_i+x \ge m\) 或者 \(a_i+y \le m-1\) 的限制。
  • \(x>y\)
    这个就只能是前面的取模,后面的不取摸,那么答案限制为 \(a_i+x\ge m 且a_i+y\le m-1\)
    有了这些转化,然后作前缀和差分,就会有值域二维数点了。
    对于模数 \(m\) 根号分治,由于 \(n,V\) 同阶,使用 \(n\) 代替 \(V\)
  • \(m\le\sqrt{n}\)
    枚举 \(m\) 的值,直接将数组里面所有的数模 \(m\) 的值存下来乱搞即可。
    比如说计 f[i][j] 表示当前加入的点中取模 \(i\) 等于 \(j\) 的数的个数,那么值域范围是\(\sqrt{n}\) 的,直接暴力枚举。
    你可以拆成两个询问在二维数点里 \(O(\sqrt{n})\) 次动态加点,这样添加的复杂度也是\(O(n\sqrt{n})\)
  • \(m>\sqrt{n}\)
    考虑对每次暴力查询加上 \(m\) 取值的作暴力查询,如果用常规树状数组的\(O(n\log n)\)查询和修改,是无法通过的。
    对于这个玩意作根号平衡,\(O(\sqrt{n})\) 修改,\(O(1)\)查询即可通过。
/*
雲璃猫猫が好きです
すべての生命よ,歌のように輝いています
截剣式、斬、断、破です!
*/
#include<bits/stdc++.h>
#include<bits/extc++.h>
#define int long long
#define INF 1e18
#define lb long double
#define ls (id<<1)
#define rs (id<<1|1)
#define rep(i,l,r,k) for(int i=(l);i<=(r);i+=(k))
#define dep(i,r,l,k) for(int i=(r);i>=(l);i-=(k))
#define tep(ch,cr) for(auto ch:cr)
#define mk make_pair
#define me(a,b) memset(a,b,sizeof(a))
#define pb push_back
#define pr putchar
#define fi first
#define se second
#define wl while
#define max(a,b)((a)>(b)?(a):(b))
#define min(a,b)((a)<(b)?(a):(b))
#define debug puts("____________________________________________________________________________")
using namespace std;
random_device rd;
unsigned int seed=rd();
mt19937 Rand(seed);
typedef pair<int,int> pii;
const int M=1e5+110,mod=1e9+7,Mod=998244353,C=349;
__gnu_pbds::gp_hash_table<string,int>ml;
inline int read(){int sum=0,k=1;char c=getchar();
	while(c>'9'||c<'0'){if(c=='-')k=-1;c=getchar();
	}while(c>='0'&&c<='9'){sum=sum*10+c-48;c=getchar();
	}return sum*k;
}inline void wr(int x){if(x<0) putchar('-'),x=-x;
	if(x>9) wr(x/10);return void(putchar(x%10+'0'));}
int n,q,a[M],ans[M*5],bl=C;
int cnt[450][450],sum[450][450],pre[450],L[450],R[450],pos[M];
struct Node{int l,r,m,id,val;};vector<Node>qry[M];
signed main(){
	n=read(),q=read();
	rep(i,1,(n-1)/bl+1,1){
		L[i]=(i-1)*bl+1;R[i]=min(n,i*bl);
		rep(j,L[i],R[i],1) pos[j]=i,a[j]=read();
	}
	rep(i,1,q,1){
		int l=read(),r=read(),x=read(),y=read(),m=read();x%=m;y%=m;
		if(x<y){
			qry[r].pb(Node{0,m-y-1,m,i,1});
			qry[r].pb(Node{m-x,m-1,m,i,1});
			qry[l-1].pb(Node{0,m-y-1,m,i,-1});
			qry[l-1].pb(Node{m-x,m-1,m,i,-1});
		}else if(x>y){
			qry[r].pb(Node{m-x,m-y-1,m,i,1});
			qry[l-1].pb(Node{m-x,m-y-1,m,i,-1});
		}
	}
	rep(i,1,n,1){
		rep(j,1,C,1) cnt[j][a[i]%j]++;
		int pl=pos[a[i]],lp=a[i]-((pl-1)*bl);
		rep(j,lp,bl-1,1) sum[pl][j]++;
		rep(j,pl,pos[n],1) pre[j]++;
		tep(Q,qry[i]){
			if(Q.m<C){
				int res=0;
				rep(j,Q.l,Q.r,1) res+=cnt[Q.m][j];
				ans[Q.id]+=res*Q.val;
			}else{
				int res=0;
				rep(i,0,M-1,Q.m){
					int l=max(0,i+Q.l-1),r=min(i+Q.r,M-1);
					if(l>1e5)break;
					int p=pos[l],q=pos[r];
					int idl=l-p*C+C,idr=r-q*C+C;
					if(p==q)res+=sum[p][idr]-sum[p][idl];
					else res+=pre[q-1]+sum[q][idr]-pre[p-1]-sum[p][idl];
				}
				ans[Q.id]+=res*Q.val;
			}
		}
	}
	rep(i,1,q,1) wr(ans[i]),pr(10);
	return 0;
}
/*

*/
posted @ 2025-06-25 21:05  rerecloud  阅读(83)  评论(1)    收藏  举报