根号分治(根号算法)

是根号算法,然而不是分块;

是论文,然而不是莫队;

是暴力美学,然而不是暴力。

例题

这两题貌似没有区别,我们以 R e m a i n d e r P r o b l e m Remainder Problem RemainderProblem 作为例子来介绍。

题目描述

给你一个长度为 500000 500000 500000 的序列,初值为 0 0 0 ,你要完成 q q q 次操作,操作有如下两种:

  1. 1 x y : 将下标为 x x x 的位置的值加上 y y y
  2. 2 x y : 询问所有下标模 x x x 的结果为 y y y 的位置的值之和

思路

这道题要我们求的,就是以下程序:

for(int i=y;i<=n;i+=x)
    ans+=value[i];

但,这样暴力求解显然超时。

我们发现:

如果 x x x 很小时,那上面的那层 f o r for for 循环的复杂度更接近 O ( n ) O(n) O(n);反之,如果 x x x 很大时,复杂度反而会变小。

这样我们就有了一种思路,

x x x 比较大的时候我们才暴力,如果 x x x 很小,我们就直接记录答案!!!

这就是 根号分治 根号分治 根号分治 的思想,将询问根据数据大小分成两个部分,分别用不同的方法来做。

那分界线是多少呢?

一般规定,是 n \sqrt{n} n

即, x > n x > \sqrt{n} x>n 时,就暴力求解; x ≤ n x \le \sqrt{n} xn 时,则预处理出答案。

如此,当数据 ≤ n \le \sqrt{n} n 时,每次修改的时间复杂度为为 O ( n ) O(\sqrt{n}) O(n ) ,查询的为 O ( 1 ) O(1) O(1)

当数据 > n > \sqrt{n} >n 时,每次修改的时间复杂度为 O ( 1 ) O(1) O(1) ,查询的为 O ( n ) O(\sqrt{n}) O(n )

通过根号分治,我们就能实现两种操作的时间复杂度均分 。

对于预处理,我们用 f i , j f_{i,j} fi,j 表示所有下标模 i i i j j j 的数的和。

f f f 数组只需开到 n ∗ n \sqrt{n} * \sqrt{n} n n ,所以空间也不会爆。

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+5,N=5e5,sqrtn=710;
int q;
long long a[maxn],f[sqrtn][sqrtn];
int main(){
	cin>>q;
	int opt,x,y,tmp=sqrt(N);
	while(q--){
		scanf("%d%d%d",&opt,&x,&y);
		if(opt==1){ //修改操作 
			for(int i=1;i<=tmp;i++)
				f[i][x%i]+=y;
			a[x]+=y;
		}
		else{ //查询操作 
			if(x<=tmp) printf("%lld\n",f[x][y]);
			else{
				long long ans=0;
				for(int i=y;i<=N;i+=x)
					ans+=a[i];
				printf("%lld\n",ans);
			}
		}
	}
	return 0;
}

注意

  • 其实在代码实现的过程中,修改操作并不能分类处理
  • 注意数组开的大小,是 n \sqrt{n} n ,以免出错,避免调试时难以发觉!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值