浅谈根号分治

根号分治

根号分治是一种优美的暴力。

顾名思义,根号分治就是对于一个长度为 N N N的数列,将查询和修改分为 ≤ N \leq N N > N >N >N的两个部分来处理。将两个部分分别处理并拼在一起,来优化时间复杂度。


例题

洛谷P3396 哈希冲突

有一个长度为 n n n的序列 a a a m m m次操作,每次操作有两个类型:

  • 查询所有满足 k % x = y k\% x=y k%x=y a k a_k ak之和
  • a x a_x ax修改为 y y y

1 ≤ n ≤ 150000 , 1 ≤ m ≤ 150000 , 1 ≤ a i ≤ 1000 1\leq n\leq 150000,1\leq m\leq 150000,1\leq a_i\leq 1000 1n150000,1m150000,1ai1000

解题思路

首先,我们考虑暴力。

code

#include
using namespace std;
int n,m,B,x,y,ans,a[150005];
char ch;
int main()
{
	scanf("%d%d",&n,&m);
	B=sqrt(n);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	while(m--){
		ch=getchar();
		while(ch!='A'&&ch!='C') ch=getchar();
		scanf("%d%d",&x,&y);
		if(ch=='A'){
			ans=0;
			for(int i=y;i<=n;i+=x) ans+=a[i];
			printf("%d\n",ans);
		}
		else a[x]=y;
	}
	return 0;
}

这个暴力是 O ( n m ) O(nm) O(nm)的,我们考虑优化。

我们发现,如果当前是第一种操作且 x > n x>\sqrt n x>n 时,一次查询是 O ( n ) O(\sqrt n) O(n )的。然而,在 x ≤ n x\leq \sqrt n xn 时,时间复杂度就变大了。我们考虑对 x ≤ n x\leq \sqrt n xn 的部分特殊处理一下。

f i , j f_{i,j} fi,j表示模数为 i i i时所有下标模 i i i j j j的数之和,那么我们用 O ( n n ) O(n\sqrt n) O(nn )处理出 1 ≤ i ≤ s q r t n 1\leq i\leq sqrt n 1isqrtn 0 ≤ j < i 0\leq j0j<i的所有 f i , j f_{i,j} fi,j之和。

那么,在查询时,若 x ≤ n x\leq \sqrt n xn ,直接在 f i , j f_{i,j} fi,j O ( 1 ) O(1) O(1)查询;若 x > n x>n x>n,则 O ( n ) O(\sqrt n) O(n )暴力查询。在修改时,我们 O ( n ) O(\sqrt n) O(n )修改 f i , j f_{i,j} fi,j,再 O ( 1 ) O(1) O(1)修改 a i a_i ai,那么时间复杂度就降为 O ( ( n + m ) n ) O((n+m)\sqrt n) O((n+m)n )

code

#include
using namespace std;
int n,m,B,x,y,ans,a[150005],f[405][405];
char ch;
int main()
{
	scanf("%d%d",&n,&m);
	B=sqrt(n);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	for(int i=1;i<=B;i++){
		for(int j=1;j<=n;j++) f[i][j%i]+=a[j];
	}
	while(m--){
		ch=getchar();
		while(ch!='A'&&ch!='C') ch=getchar();
		scanf("%d%d",&x,&y);
		if(ch=='A'){
			if(x<=B) printf("%d\n",f[x][y]);
			else{
				ans=0;
				for(int i=y;i<=n;i+=x) ans+=a[i];
				printf("%d\n",ans);
			}
		}
		else{
			for(int i=1;i<=B;i++) f[i][x%i]+=y-a[x];
			a[x]=y;
		}
	}
	return 0;
}

你可能感兴趣的:(算法,算法,c++)