根号分治简介
RedLycoris · · 个人记录
根号分治,顾名思义,就是将数据范围
还是先给大家一个简单例题引入一下吧:
CF 1207F. Remainder Problem
(没错就是这场EducationalRound 我只做了ACEF...)
题目大意:
初始有一个序列,长度为
然后有
第一种操作是将
第二种操作就是求出
如果你在3s内就看出这题的做法,那么您就可以不用看这篇日报了。。。
如果暴力的话,那么我们可以发现,复杂度是
所以我们可以将求和操作拆成两部分:
如果
反之,那么循环累加的次数一定是小于等于
总复杂度撒花
Code:
//这个751只是随便找了一个与sqrt(n)接近的数。这种数都是玄学。
#include<bits/stdc++.h>
#define ll long long
#define mp make_pair
using namespace std;
ll sum[755][755],a[500005];
int main(){
ios_base::sync_with_stdio(false);cin.tie(0),cout.tie(0);
int q;cin>>q;
for(;q--;){
int tp,x,y;cin>>tp>>x>>y;
if(tp==1){
for(int i=1;i<751;++i)sum[i][x%i]+=y;
a[x]+=y;
}else{
if(x<751){
cout<<sum[x][y]<<endl;
}else{
ll rt=0;
for(int i=y;i<=500000;i+=x)rt+=a[i];
cout<<rt<<endl;
}
}
}
}
好了,到了这里,大家应该明白根号分治的基础思路了吧。就是利用复杂度接近反比例函数的性质,将前面的
再给大家介绍一道例题吧
[APIO2015]雅加达的摩天楼
题意十分明了。
如果你搜索过了那么还是想想正解吧
题解:借用了@GoldenPotato137 大佬的题解Orz (写不过TA)
有一个一眼贪心想法:对于每只doge,我们都暴力地去建它连向它能跳到的点的边,边权为跳的次数。然后直接求一遍单元最短路即可。
很显然,这玩意的边的数量是
这显然是要T飞的,但是我们会从中发现一个问题:既然一个doge的跳跃是多步的,那我们能否直接把几步拆开来,然后省略重复的边? 例如:
优化为: 这样做看起来很星,很不幸的是,这样是不行的。因为我们在计算最短路的时候,我们有可能直接从中间某个点出发,但是很不幸的是,这样是不可行的。
怎么办呢?这时我们可以考虑把网络流的分层图那一套搬出来。 我们可以考虑使用“拆点”的做法来限制从某个点出发去更新别的点的最短路。 考虑把一个点拆分为size个点,每个拆分点的含义为所有一次跳x步的都从它出发,并到达它那里。
从每个点的拆分点出发,向它的原点连一条边权为0的有向边
如果能从某个点出发,则对应的从原点连向那个跳x格远的分点连一条边权为0的边
接下来我们从每个点的对应的跳x格远的的点连向其他的点的跳x格的点,边权为1
一图胜千言: 变为 最后一行的所有点即为原来的点 从下往上第x行的点即为某个原点的第x个分点
通过这样一轮拆分,我们就已经可以解决问题啦。
啥?你说这样会有
同样,这个分界点的取值还是一个玄学的数。
敲黑板 这里是重点!
Code:
// luogu-judger-enable-o2
#include<iostream>
#include<cstdio>
#include<vector>
#include<cmath>
#include<cstring>
using namespace std;
long long read()
{
long long x=0,f=1; char c=getchar();
while(!isdigit(c)){if(c=='-') f=-1;c=getchar();}
while(isdigit(c)){x=x*10+c-'0';c=getchar();}
return x*f;
}
const int N=30000+100;
const int M=100+20;
struct edge
{
int to,w;
edge (int x,int y)
{
to=x,w=y;
}
};
vector <edge> e[N*M];
int n,m,size,dis[N*M],S,T;
void spfa()
{
static int InQueue[N*M],mqueue[N*M*10],front,tail;
memset(dis,0x3f,sizeof dis);
front=tail=0;
mqueue[tail++]=S*size,dis[S*size]=0;
while(tail>front)
{
int now=mqueue[front++];
InQueue[now]=false;
for(int i=0;i<int(e[now].size());i++)
if(dis[e[now][i].to]>dis[now]+e[now][i].w)
{
dis[e[now][i].to]=dis[now]+e[now][i].w;
if(InQueue[e[now][i].to]==false)
{
InQueue[e[now][i].to]=true;
mqueue[tail++]=e[now][i].to;
}
}
}
}
int main()
{
//freopen("3645.in","r",stdin);
//freopen("3645.out","w",stdout);
int t=clock();
n=read(),m=read();
size=min(int(sqrt(n)),50);
int to=n*size;
for(int i=1;i<=to;i++)
e[i].reserve(4);
for(int i=0;i<n;i++)
for(int j=1;j<size;j++)
e[i*size+j].push_back(edge(i*size,0));
for(int i=1;i<=m;i++)
{
int b=read(),p=read();
if(i==1) S=b;
if(i==2) T=b;
if(p>=size)
{
for(int j=b+p,k=1;j<n;j+=p,k++)
e[b*size].push_back(edge(j*size,k));
for(int j=b-p,k=1;j>=0;j-=p,k++)
e[b*size].push_back(edge(j*size,k));
}
else
{
e[b*size].push_back(edge(b*size+p,0));
for(int j=b;j<n-p;j+=p)
{
bool OK=false;
for(int k=0;k<int(e[j*size+p].size());k++)
if(e[j*size+p][k].to==(j+p)*size+p)
{
OK=true;
break;
}
if(OK==true) break;
e[j*size+p].push_back(edge((j+p)*size+p,1));
}
for(int j=b;j>=p;j-=p)
{
bool OK=false;
for(int k=0;k<int(e[j*size+p].size());k++)
if(e[j*size+p][k].to==(j-p)*size+p)
{
OK=true;
break;
}
if(OK==true) break;
e[j*size+p].push_back(edge((j-p)*size+p,1));
}
}
}
spfa(); //求最短路
if(dis[T*size]<0x3f3f3f3f)
printf("%d",dis[T*size]);
else
printf("-1");
cerr<<clock()-t;
return 0;
}
其实这一类的题目数量并不多,但是遇上了估计也只用这样做才可以了。
还是找到了一道例题:CF1039D You Are Given a Tree
放在结尾的BB:
俗话所得好,“调参要从娃娃抓起”
这个临界值就是一个标准的调参。
理论上来说是设置在TLE)后才能得到。
作者时比较喜欢用
int size=pow(n,0.618);
完结撒花