关于根号分治

根号分治就是暴力+暴力!——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;
}
posted @ 2025-06-25 21:05  rerecloud  阅读(70)  评论(1)    收藏  举报