(简记)根号分治

Intro

收藏一篇好文

你需要知道,这是一种辅助优化暴力的技巧,通常通过设置一个阈值 \(b\) 选择两种暴力的其中一种,因为最终 \(b\) 一般取 \(\sqrt{n}\) 最优故有根号分治之称。

例题

P8572 [JRKSJ R6] Eltaw

这题的根号是全局的,给出了行数列数 \(nk \le 5\times 10^5\) 的条件,则必然有 \(n,k\) 其中之一 \(\le \sqrt{nk}\le \sqrt{5\times 10^5}\),分别考虑。

对于 \(k\le \sqrt{nk}\),直接暴力扫每行,前缀和 \(O(1)\) 查询即可,时间 \(O(nk+q\sqrt{nk})\)

对于 \(n\le \sqrt{nk}\),我们发现不同的区间总共只有 \(n^2\le nk\le 5\times 10^5\) 个,那么就说明这 \(q\) 次询问中有挺多应该是重复的,那么对于 \(k\) 行,每行用其前缀和更新 \(f_{l,r}\) 表示询问 \([l,r]\) 的答案,预处理只需要 \(O(n^2k)\)

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=5e5+5;
int n,k,q;
vector<LL>sum[N];
LL res[710][710];
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>k>>n>>q;
	for(int i=0;i<n;i++){
		LL pre=0;
		for(int j=0;j<k;j++){
			LL x;cin>>x;pre+=x;
			sum[i].push_back(pre);
		}
	}
	if(n<=k){
		while(q--){
			int l,r;
			LL mx=0;
			cin>>l>>r;
			l--,r--;
			for(int i=0;i<n;i++)
				mx=max(mx,sum[i][r]-(l>0?sum[i][l-1]:0));
			cout<<mx<<'\n';
		}
	}
	else {
		for(int i=0;i<k;i++)
			for(int j=i;j<k;j++){
				for(int ID=0;ID<n;ID++){
					res[i][j]=max(res[i][j],sum[ID][j]-(i>0?sum[ID][i-1]:0));
				}
			}
		while(q--){
			int l,r;
			cin>>l>>r;
			l--,r--;
			cout<<res[l][r]<<'\n';
		}
	}
	return 0;
}

P3396 哈希冲突

如题,该题有两种暴力的方案。

  1. \(O(1)\) 修改 \(val_i\),然后 \(O(\frac{n}{x})\) 查询。
  2. \(O(b)\) 修改一个代表 \(i\bmod x=y\) 的位置的和的 \(c_{x,y}\),其中 \(x\) 是自变量,\(y\) 是因变量,然后 \(O(1)\) 查询。

对于方案 1,我们希望 \(\frac{n}{x}\) 尽量小,对于方案 2,我们又希望 \(O(b)\) 尽量小。我们可以把两种暴力混搭,如果 \(x\le b\) 就使用方案 2,否则使用方案 1。那么每次修改就需要修改 \(O(b)\) 次,把 \(\le b\)\(c\) 全部修改,然后改掉 \(val_i\)。每次查询时如果 \(x\le b\) 就用方案 2 \(O(1)\) 查询,否则用方案 \(1\) \(O(\frac{n}{x})\) 查询。

可以发现,由于用 2 时 \(x>b\implies \frac{n}{x}<\frac{n}{b}\),意味着 \(O(\frac{n}{x})\) 其实就是 \(O(\frac{n}{b})\) 的,这样我们就通过一个阈值 \(b\) 把每次操作次数上限变成了 \(\max(\frac{n}{b},b)\) 的,显然在两者相等时取最小,\(b=\sqrt{n}\),时间 \(O(m\sqrt{n})\)

#include<bits/stdc++.h>
using namespace std;
const int sN=1e3+5,N=1.5e5+5;
int n,m,b,c[sN][sN],val[N];
int main(){
	scanf("%d%d",&n,&m);b=sqrt(n);
	for(int i=1;i<=n;i++){
		scanf("%d",&val[i]);
		for(int j=1;j<=b;j++)
			c[j][i%j]+=val[i];
	}
	while(m--){
		char ch[1];
		scanf("%s",ch);
		int x,y;
		scanf("%d%d",&x,&y);
		if(ch[0]=='A'){
			if(x<=b)printf("%d\n",c[x][y]);
			else {
				int sum=0;
				for(int i=y;i<=n;i+=x)
					sum+=val[i];
				printf("%d\n",sum);
			}
		}
		else {
			for(int j=1;j<=b;j++)
				c[j][x%j]-=val[x];
			val[x]=y;
			for(int j=1;j<=b;j++)
				c[j][x%j]+=val[x];
		}
	}
	return 0;
}

P5309 [Ynoi2011] 初始化

根据 trick,我们在修改时,当 \(x>b\),选择暴力修改,先分块(块长可取 \(\sqrt{n}\)),然后对于每个块记录 \(sum\),修改的时候同时改单个值和块的 \(sum\)。这样查询的时候就可以 \([l,r]\) 中整块直接累加,散块暴力处理。

\(x\le b\),我们选择用一个 \(c_{i,j}\) 记录修改时 \(i=x,j=y\)\(z\) 的总和。这似乎不是很够,那么我们对 \(j\) 这一维做一个前后缀和,这样修改的时候要修改最多 \(x\) 次,查询的时候将序列视为关于 \(x\) 块长的分块,整块一起处理散块前后缀累加即可。

这样的时间理论上可以达到 \(O(m(\sqrt{n}+b+\frac{n}{b}))\),然而由于查询操作中逐个访问 \(1\)\(b\) 常数有点大,所以 \(b\)\(\sqrt{n}\) 可能有点小问题,根据讨论区适当卡常。(?

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL MOD=1e9+7;
const int sN=1e3+5,N=2e5+5;
int n,m,bs;
LL pre[sN][sN],suf[sN][sN],a[N],sum[sN];
int b[N],L[sN],R[sN];
inline void Mo(LL &a){a>MOD?a-=MOD:1;}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>m;
	bs=300;
	for(register int i=1;i<=n;i++){
		cin>>a[i];
		a[i]%=MOD;
		b[i]=(i-1)/bs+1;
		if(!L[b[i]])L[b[i]]=i;
		R[b[i]]=i;
		sum[b[i]]+=a[i];
		Mo(sum[b[i]]);
	}
	bs=160;
	while(m--){
		int op;
		cin>>op;
		if(op==1){
			int x,y;
			LL z;
			cin>>x>>y>>z;
			y%=x;z%=MOD;
			if(x>bs){
				for(register int i=y;i<=n;i+=x){
					a[i]+=z,Mo(a[i]);
					sum[b[i]]+=z,Mo(sum[b[i]]);
				}
			}
			else {
				for(register int i=0;i<=y;i++)
					suf[x][i]+=z,Mo(suf[x][i]);
				for(register int i=y;i<x;i++)
					pre[x][i]+=z,Mo(pre[x][i]);
			}
		}
		else {
			int l,r;
			cin>>l>>r;
			LL res=0;
			if(b[l]==b[r])
				for(register int i=l;i<=r;i++)
					res+=a[i];
			else {
				for(register int i=b[l]+1;i<=b[r]-1;i++)
					res+=sum[i];
				for(register int i=l;i<=R[b[l]];i++)
					res+=a[i];
				for(register int i=L[b[r]];i<=r;i++)
					res+=a[i];
			}
			res%=MOD;
			for(register int x=1;x<=bs;x++){
				LL sum=suf[x][0];
				res+=max(0,r/x-l/x-1)*sum;
				if(l/x==r/x)
					res+=pre[x][r%x]-(l%x?pre[x][l%x-1]:0);
				else {
					res+=suf[x][l%x];
					res+=pre[x][r%x];
				}
			}
			cout<<(res%MOD+MOD)%MOD<<'\n';
		}
	}
	return 0;
}
posted @ 2025-07-09 07:47  TBSF_0207  阅读(24)  评论(0)    收藏  举报