是根号算法,然而不是分块;
是论文,然而不是莫队;
是暴力美学,然而不是暴力。
例题
- 哈希冲突
- R e m a i n d e r P r o b l e m Remainder Problem RemainderProblem
这两题貌似没有区别,我们以
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 x y
: 将下标为 x x x 的位置的值加上 y y y -
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} x≤n 时,则预处理出答案。
如此,当数据 ≤ 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 的数的和。文章来源:https://www.toymoban.com/news/detail-841839.html
f f f 数组只需开到 n ∗ n \sqrt{n} * \sqrt{n} n∗n ,所以空间也不会爆。文章来源地址https://www.toymoban.com/news/detail-841839.html
代码
#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 ,以免出错,避免调试时难以发觉!
到了这里,关于根号分治(根号算法)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!