RedLycoris
2022-09-25 16:12:57
根号分治,是暴力美学的集大成体现。与其说是一种算法,我们不如称它为一个常用的trick。
首先,我们引入一道入门题目 CF1207F Remainder Problem:
给你一个长度为
1 x y
: 将下标为 2 x y
: 询问所有下标模 考虑这题的暴力是什么。
首先有一种暴力就是按照题目所说的去做,开一个
对于这种暴力,
经过思考,我们可以发现另外一种暴力:新开一个大小为
对于这种暴力,
现在我们发现,这两种暴力对应了两种极端:一个是
其实是有的。我们设定一个阈值
对于所有
对于所有
所以,总时间复杂度就成为了
代码:
#include<bits/stdc++.h>
#define ll long long
#define mp make_pair
using namespace std;
ll sum[755][755],a[500005]; //这里我的阈值取了700
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<700;++i)sum[i][x%i]+=y; //枚举模数
a[x]+=y;
}else{
if(x<700){
cout<<sum[x][y]<<endl;
}else{
ll rt=0;
for(int i=y;i<=500000;i+=x)rt+=a[i]; //暴力统计
cout<<rt<<endl;
}
}
}
}
通过一道例题,我们可以发现,根号分治不就是两个暴力揉到一起嘛!对于小于一个阈值的数据我们采取一种暴力形式,对于大于阈值的数据我们采用另外一种形式。通常,这两种暴力,一种是直接模拟,另外一种是动态的维护什么东西,或者预处理。暴力与暴力的完美结合,就能过题,有时候甚至可以爆踩标算。
根号分治的用处很广,不仅可以用于数据结构,也可以用在其他方面。现在由笔者继续介绍几道例题。
1.CF710D Two Arithmetic Progressions
题目大意:
现在有两个等差数列,形如
题解:
正解要用到 exgcd 等数论知识,且细节较多比较麻烦。现在我们考虑如何用根号分治解决该数论问题。
现在钦定
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll a,b,c,d,l,r;
int main(){
ios_base::sync_with_stdio(false);
cin>>a>>b>>c>>d>>l>>r;
ll m=max(a,c);
if(m<=50000){ //存在循环节,找到第一个就行了
for(int i=-m*2;i<=m*2;++i){
ll p=i*a+b;
if((abs(d-p)%c==0)){
ll fg=__gcd(a,c);
fg=a*c/fg;
ll bg=max(max(b,d),l);
if(p>bg){
p=bg+(p-bg)%fg;
}else{
p+=((bg-p)/fg+1)*fg;
p=bg+(p-bg)%fg;
}
if(r<p)continue;
cout<<(r-p)/fg+1<<'\n';
return 0;
}
}
cout<<0<<'\n';
return 0;
}else{
if(a<c)swap(a,c),swap(b,d);
ll cnt=0;
for(ll i=b;i<=r;i+=a){ //直接暴力去找
if(i>=l and i>=d){
ll del=i-d;
if(del%c==0)++cnt;
}
}
cout<<cnt<<endl;;
}
}
2.洛谷P8250 交友问题
题目大意:
洛谷上有
洛谷的图片分享机制如下:如果第
现在,用户
对于
题解:
这题看上去就很不可做的样子,没有什么优秀的
我们注意到这题其实还有一个限制:所有点的度数之和为
这句话看起来是废话,但 某个值的总数量一定 是根号分治的一个明显的提示条件。
所以我们可以这样分治:
暴力
利用 map 存储所有的出边,每次询问时暴力枚举所有邻居并在 map 中判断是否出现过。
时间复杂度:
暴力
对于每一个节点都开一个 bitset 存储邻接点。(设为
查询用
时间&空间复杂度:
正解:
考虑将 暴力
我们设定一个阈值
显然,由于总度数是
查询的时候分类讨论(注意
通过对
Code:
#include<bits/stdc++.h>
#define ll long long
#define reg register
#define mp make_pair
#define ri register int
#define ld long double
using namespace std;
const int mxn=2e5+5;
vector<int>g[mxn];
int n,m,q,lim;
vector<int>heavy;
int id[mxn]; //存是否为重点,并存是第几个重点
vector<bitset<mxn> >T;
map<int,int>have[mxn];
int deg[mxn];
inline void solve(){
scanf("%d%d%d",&n,&m,&q);
for(ri i=1,u,v;i<=m;++i){
scanf("%d%d",&u,&v);
g[u].push_back(v);
g[v].push_back(u);
++deg[u];
++deg[v];
}
sort(deg+1,deg+n+1);reverse(deg+1,deg+n+1);
lim=deg[min(n,(int)(sqrt(n)*6))];//根号处分治
memset(id,-1,sizeof(id));
for(ri i=1;i<=n;++i){
if(g[i].size()>lim){
heavy.push_back(i);
id[i]=heavy.size()-1;bitset<mxn>newb;
newb&=0;
for(ri j=0;j<g[i].size();++j)newb[g[i][j]]=1;
T.push_back(newb);
}else{
for(ri j=0;j<g[i].size();++j)have[i][g[i][j]]=1;
}
}
for(ri i=1;i<=q;++i){
ri u,v;scanf("%d%d",&u,&v);
if(~id[u] and ~id[v]){ //对两个点是否是重点分类讨论
bitset<mxn>tmp=T[id[u]]^(T[id[u]]&T[id[v]]);
tmp[u]=0;tmp[v]=0;
printf("%d\n",tmp.count());
}else if(~id[u]){
ri ans=0;
for(ri j=0;j<g[v].size();++j)if(g[v][j]==u or g[v][j]==v or T[id[u]][g[v][j]])++ans;
printf("%d\n",T[id[u]].count()-ans);
}else if(~id[v]){
ri ans=0;
for(ri j=0;j<g[u].size();++j)if(g[u][j]!=u and g[u][j]!=v and !T[id[v]][g[u][j]])++ans;
printf("%d\n",ans);
}else{
ri ans=0;
for(ri j=0;j<g[u].size();++j)if(g[u][j]!=u and g[u][j]!=v and !have[v][g[u][j]])++ans;
printf("%d\n",ans);
}
}
}
int main(){
solve();
return 0;
}
3.洛谷P5901 [IOI2009] regions
题目大意:
联合国区域发展委员会(The United Nations Regional Development Agency,UNRDA)有一个良好的组织结构。
它任用了
委员们按照其资历被编号为
除了主席之外所有委员都有一个直接导师。任何直接导师的资历都比他所指导的委员的资历要高。
我们说委员
现在,联合国区域发展委员会想要建立一个计算机系统:在给定委员之间的直接导师关系的情况下,该系统可以自动地回答下述形式的问题:给定两个地区
对于
题解:
看到每个点都有颜色,然后询问和颜色的种类有关,时限开了很【】的 8s,就可以往根号分治方面想了。
按照常理,我们钦定一个
如果
如果
同理,如果
最后,如果
以上是会根号分治的人都可以想到的做法,但如果实现不精细的话,时间复杂度会写成
这就是很多根号分治要面对的问题:卡 常。
针对这题可以如此实现:
现在的时间复杂度已经降到了正好的
为了减小常数:
代码:
const int mxn=2e5+5;
vector<int>g[mxn];
int n,R,q,r[mxn];
int dep[mxn*2];
int ord[mxn*2],cco,cord[mxn*2];
int mi[mxn*2][22];
int lg[mxn*2],co;
int st[mxn*2],ed[mxn*2];
inline void dfs(int x,int par=0,int deep=1){
cord[++cco]=x;
st[x]=cco;ed[x]=cco;
dep[x]=deep;ord[x]=++co;
for(int y:g[x]){
if(par==y)continue;
dfs(y,x,deep+1);
cord[++cco]=x;
ed[x]=cco;
}
}
inline void init(){ //预处理ST表
lg[1]=0;
for(int i=2;i<mxn*2;++i)lg[i]=lg[i>>1]+1;
for(int i=1;i<=cco;++i)mi[i][0]=cord[i];
for(int k=1;k<22;++k){
for(int i=1;i<=cco-(1<<k)+1;++i){
int x=mi[i][k-1],y=mi[i+(1<<(k-1))][k-1];
if(dep[x]<dep[y])mi[i][k]=x;
else mi[i][k]=y;
}
}
}
inline int lca(int x,int y){
int fx=st[x],fy=ed[y];
if(fx>fy)swap(fx,fy);
int ax=mi[fx][lg[fy-fx]],ay=mi[fy-(1<<lg[fy-fx])+1][lg[fy-fx]];
if(dep[ax]<dep[ay])return ax;
else return ay;
}
inline bool cmp(int x,int y){return ord[x]<ord[y];}
int sta[mxn],top=0;
int sz[mxn];
int up[404][mxn],dw[404][mxn];
inline vector<int>mer(vector<int>x,vector<int>y){ //类似归并的操作
vector<int>rt;rt.clear();
int i=0,j=0;
for(;i<x.size() and j<y.size();)
if(cmp(x[i],y[j]))rt.push_back(x[i]),++i;
else rt.push_back(y[j]),++j;
for(;i<x.size();++i)rt.push_back(x[i]);
for(;j<y.size();++j)rt.push_back(y[j]);
return rt;
}
inline void gen(vector<int>v,int y){ //建立虚树
top=0;
sta[++top]=1;
sz[1]=r[1]==y;
for(int i:v){
if(i!=1){
int l=lca(sta[top],i);
if(l!=sta[top]){
for(;ord[l]<ord[sta[top-1]];)sz[sta[top-1]]+=sz[sta[top]],--top;
if(ord[l]>ord[sta[top-1]]){
sz[l]=r[l]==y;
sz[l]+=sz[sta[top]];
sta[top]=l;
}else sz[l]+=sz[sta[top]],top--;
}
sz[i]=r[i]==y;
sta[++top]=i;
}
}
for(int i=top-1;i>=1;--i)sz[sta[i]]+=sz[sta[i+1]];
}
vector<int>col[mxn];
vector<int>hea;
int ans[404][404];
int id[mxn];
//inline void getans(int x,int fa,int cl){//on ng[] //原来的暴力统计小对小和大对大的答案
// if(r[x]==cl)sz[x]=1;
// else sz[x]=0;
// for(int y:ng[x])if(y!=fa)getans(y,x,cl),sz[x]+=sz[y];
//}
inline void prep(int x,int fa,int cl,int flg=0){ //预处理小对大和大对小的up和dw数组
if(r[x]==hea[cl])dw[cl][x]=1,++flg;
up[cl][x]=flg;
for(int y:g[x])if(y!=fa){
prep(y,x,cl,flg);
dw[cl][x]+=dw[cl][y];
}
}
inline void solve(){
memset(id,-1,sizeof(id));
read(n),read(R),read(q);
read(r[1]);col[r[1]].push_back(1);
for(int i=2;i<=n;++i){
int x;read(x),read(r[i]);
g[x].push_back(i);
g[i].push_back(x);
col[r[i]].push_back(i);
}
dfs(1);
init();
int bound=500;
for(int i=1;i<=R;++i)if(col[i].size()>=bound)hea.push_back(i); //这里我的B定为了500
for(int i=0;i<hea.size();++i)id[hea[i]]=i;
for(int i=1;i<=n;++i)sort(col[i].begin(),col[i].end(),cmp);
for(int i=0;i<hea.size();++i)for(int j=0;j<hea.size();++j)if(i!=j){
gen(mer(col[hea[i]],col[hea[j]]),hea[j]);
// getans(1,0,hea[j]);
ll res=0;
for(int f:col[hea[i]])res+=sz[f];
ans[i][j]=res;
}
for(int i=0;i<hea.size();++i)prep(1,0,i);
for(;q--;){
int x,y;read(x),read(y);
if(~id[x] and ~id[y])print(ans[id[x]][id[y]]),putchar('\n');
else if(~id[x] and id[y]==-1){
ll res=0;
for(int i:col[y])res+=up[id[x]][i];
print(res),putchar('\n');
}else if(id[x]==-1 and ~id[y]){
ll res=0;
for(int i:col[x])res+=dw[id[y]][i];
print(res),putchar('\n');
}else{
gen(mer(col[x],col[y]),y);
// getans(1,0,y);
ll res=0;
for(int f:col[x])res+=sz[f];
print(res),putchar('\n');
}
}
}
int main(){
ios_base::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int T=1;
//cin>>T;
for(;T--;)solve();
return 0;
}
4.[ARC052D] 9
题目大意:
给定两个正整数
题解:
这个
显然无法分块,考虑怎么做到根号分治。我们先对
当
我们可以考虑把
均衡一下取
Code:
#include<bits/stdc++.h>
#define ll long long
#define int ll
using namespace std;
ll k,m,n;
int ee[12];
ll dp[12][10000][91][2];
inline void add(ll&x,ll y){x+=y;}
signed main(){
cin>>k>>m;
ll ans=0;ll tm=m;
for(int i=1;;++i){
ee[i]=tm%10;
tm/=10;
if(tm==0){
n=i;
break;
}
}
reverse(ee+1,ee+n+1);
if(k>=10000){
for(int i=0;i<=90;++i){
for(ll ee=i;ee<=m;ee+=k){
ll t=ee,cnt=0;
while(t){
cnt+=t%10;
t/=10;
}
if(cnt%k==i)++ans;
}
}
cout<<ans-1<<'\n';
return 0;
}
dp[1][0][0][0]=1;
for(int i=1;i<=n;++i){
for(int j=0;j<k;++j){
for(int sm=0;sm<=90;++sm){
{//f=0
for(int t=0;t<ee[i];++t){
if(sm+t<=90){
add(dp[i+1][(j*10+t)%k][sm+t][1],dp[i][j][sm][0]);
}
}
if(sm+ee[i]<=90)add(dp[i+1][(j*10+ee[i])%k][sm+ee[i]][0],dp[i][j][sm][0]);
}
{//f=1
for(int t=0;t<10;++t)if(sm+t<=90)add(dp[i+1][(j*10+t)%k][sm+t][1],dp[i][j][sm][1]);
}
}
}
}
for(int ee=0;ee<=90;++ee)add(ans,dp[n+1][ee%k][ee][0]),add(ans,dp[n+1][ee%k][ee][1]);
cout<<ans-1<<'\n';
}
小结:
什么时候会用到根号分治呢?
最后,补充几道例题:
CF914F Substrings in a String
P8349 [SDOI/SXOI2022] 整数序列
CF1039D You Are Given a Tree
CF587F Duff is Mad
CF1666A Admissible Map
ABC259Ex - Yet Another Path Counting
完结撒花。