根号分治
前言
根号分治,其实就是以根号级别的阈值分界,用两种不同的方法(大部分是暴力)并在正确的复杂度内求解问题的方法。
简而言之,就是把两种暴力放一块,各自求解各自擅长的数据范围。
详解几道例题说明。
直接根号分治
CF1207F Remainder Problem
有两种暴力思路:
维护当前下标模\(x\)得\(y\)之和,但局限于空间,仅能处理模数较小的情况。
每次询问直接暴力找所有符合条件的下标对应的值,复杂度为\(O(n/k)\),模数越小越劣。
我们发现两种方法针对不同情况,因此我们各自让他们做他们自己擅长的事就好了。
设阈值为\(t\),当询问的模数小于\(t\)时,用第一种方法,时间复杂度\(O(1)\),空间复杂度\(O(t^2)\)
当模数大于\(t\)时,用第二种方法,时间复杂度\(O(n/t)\)(最坏情况)。
因此当阈值取\(\sqrt{n}\)时,空间时间达到最优。
code
#include<iostream>
#include<string.h>
#include<string>
#include<vector>
#include<bitset>
#include<set>
#include<unordered_map>
#include<queue>
#include<math.h>
#include<algorithm>
#include<iomanip>
using namespace std;
#define ll long long
const int maxn=5e5+5;
const int B=710;
ll a[maxn],b[B+2][B+2];
int q;
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>q;
for(int i=1;i<=q;i++)
{
int op,x,y;
cin>>op>>x>>y;
if(op==1)
{
a[x]+=y;
for(int j=1;j<=B;j++) b[j][(x%j)]+=y;
}
else
{
if(x<=B) cout<<b[x][y]<<'\n';
else
{
ll ret=0,sum=y;
while(sum<=5e5)
{
ret+=a[sum];
sum+=x;
}
cout<<ret<<'\n';
}
}
}
return 0;
}
根号分治与数论
CF710D Two Arithmetic Progressions
这道题当然可以直接用\(\mathcal{exgcd}\)求解,这里我们换一种思路,采用根号分治方法解决。
取\(a_1\)为题目给出的\(a_1,a_2\)的较大值,设阈值\(t\)为\(\sqrt{2e9}\)
当\(a_1<t\)时,此时\(a_2<t\)也成立,我们对于第一个等差数列的\(k\)进行枚举,从\(0\)到\(a_2\)(我们要求解一个 \(a_1*k-b \equiv 0\ mod\ a_2\) ,所以每\(a_2\)一个循环节),找不到则无解,否则对于找到的\(x\)进行加减\(lcm(a_1,a_2)\)的操作,使得\(l,k>0,x>=L,x<=R\)且\(x\)最小,若找不到则无解,否则再以\(lcm(a_1,a_2)\)为循环节,循环节个数\(+1\)(最开始找到的解),为答案。
当\(a_1>=t\)时,显然枚举所有的\(k\)的数量超不过\(\sqrt{n}\),所以直接枚举,再在第二个式子判断成立即可。
code
#include<iostream>
#include<string.h>
#include<string>
#include<vector>
#include<bitset>
#include<set>
#include<unordered_map>
#include<queue>
#include<math.h>
#include<algorithm>
#include<iomanip>
using namespace std;
#define int long long
const int B=45000;
int a1,b1,a2,b2,l,r;
int gcd(int x,int y){return y?gcd(y,x%y):x;}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>a1>>b1>>a2>>b2>>l>>r;
if(a1<a2) swap(a1,a2),swap(b1,b2);
if(a1>B)
{
int cnt=0;
for(int i=b1;i<=r;i+=a1)
{
if(i>=l&&i>=b2)
{
if((i-b2)%a2==0) cnt++;
}
}
cout<<cnt;
}
else
{
for(int i=0;i<=a2;i++)
{
int p=a1*i+b1;
if(abs(p-b2)%a2==0)
{
int gdd=gcd(a1,a2);
int lcm=a1/gdd*a2;
if(p-b2<0)
{
int cnt=ceil((double)(b2-p)/lcm);
p+=cnt*lcm;
}
if(p<l)
{
int cnt=ceil((double(l-p)/lcm));
p+=cnt*lcm;
}
if(p>r) cout<<"0\n";
else cout<<(r-p)/lcm+1<<'\n';
return 0;
}
}
cout<<"0\n";
}
return 0;
}
根号分治与图论
交友问题
分析题目,实际上是在求两点的公共邻点的个数,用\(u\)的总邻点个数减去它即为答案。
考虑用\(bitset\)高效维护交并问题,但数据范围达\(2e5\),即使\(bitset\)不怎么占空间,但面对\(4e10\)大小还是开不下的。
所以考虑根号分治,我们发现点的度总和为\(min(2m,n^2)\),度数大于\(\sqrt{n}\)的点不过就\(\sqrt{n}\)个,所以只对这些点维护\(bitset\),而对于其他点直接遍历邻点即可。
时间复杂度为\(O(n\sqrt{n})\)
code
#include<iostream>
#include<string.h>
#include<string>
#include<vector>
#include<bitset>
#include<math.h>
#include<algorithm>
using namespace std;
int n,m,q;
const int B=450;
vector<int> e[(int)2e5+5];
bitset<(int)2e5+2> a[B+2];
int in[(int)2e5+5],pos[(int)2e5+5],cnt;
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m>>q;
for(int i=1;i<=m;i++)
{
int l,r;
cin>>l>>r;
e[l].push_back(r);
e[r].push_back(l);
in[r]++;
in[l]++;
}
for(int i=1;i<=n;i++)
{
if(in[i]>B)
{
pos[i]=++cnt;
for(int j:e[i]) a[cnt][j]=1;
}
}
for(int i=1;i<=q;i++)
{
int x,y;
cin>>x>>y;
if(in[x]>B&&in[y]>B)
{
a[pos[y]][y]=1;
int ret=(a[pos[x]]^(a[pos[x]]&a[pos[y]])).count();
a[pos[y]][y]=0;
cout<<ret<<'\n';
}
else if(in[x]>B)
{
bitset<(int)2e5+2> op;
for(int j:e[y]) op[j]=1;
op[y]=1;
int ret=(a[pos[x]]^(a[pos[x]]&op)).count();
cout<<ret<<'\n';
}
else if(in[y]>B)
{
bitset<(int)2e5+2> op;
for(int j:e[x]) op[j]=1;
a[pos[y]][y]=1;
int ret=(op^(op&a[pos[y]])).count();
a[pos[y]][y]=0;
cout<<ret<<'\n';
}
else
{
bitset<(int)2e5+2> op1,op2;
for(int j:e[x]) op1[j]=1;
for(int j:e[y]) op2[j]=1;
op2[y]=1;
int ret=(op1^(op1&op2)).count();
cout<<ret<<'\n';
}
}
return 0;
}
P5901 [IOI 2009] Regions
询问对于每个颜色为\(r_1\)的点的子树里有多少个颜色为\(r_2\)的点,答案累加输出。
我们发现可以按各个颜色出现的数量将颜色分为重颜色与轻颜色,重颜色出现次数大于\(\sqrt{n}\),因此重颜色个数也就在\(\sqrt{n}\)范围内。
所以我们可以预处理出所有关于重颜色的贡献。
设\(down_{i,j}\)表示颜色\(i\)的子树中有多少重颜色\(j\)。
与此对应的\(up_{i,j}\)表示颜色\(i\)的父链上有多少重颜色\(j\)。
考虑如何预处理,进行\(dfs\)时对以上两个数组维护两个计数器。
求父链的贡献,进入节点更新计数器,离开时撤销,这样计数器始终维护链上信息。
求子树贡献,只需更新计数器,不撤销,进入节点前与离开节点时的计数器之差即为子树贡献。
预处理时间\(O(n\sqrt{n})\)
再考虑询问两个轻颜色间的贡献。(设父颜色点集为\(i\),子颜色点集为\(j\))
只需要满足\(dfn_i<=dfn_j<=out_j\)即可,拆成两段:\(dfn_j<=out_i\)的个数减去\(dfn_i>dfn_j\)的数量即可。
二维偏序,可以归并排序,但不用,只需要在\(dfs\)过程中将各点放到一个\(vector\)中,就可以自然维护大小关系,求解双指针即可。(可结合代码理解)
code
#include<iostream>
#include<string.h>
#include<string>
#include<vector>
#include<bitset>
#include<math.h>
#include<algorithm>
#include<ctime>
using namespace std;
const int maxn=2e5+5;
const int maxr=2.5e4+5;
const int B=450;
int n,r,q;
// bool ST;
vector<int> e[maxn];//边
vector<int> col[maxr];//各颜色有谁
int a[maxr],cnt;//重颜色对应下标
int down[maxr][B+2],c1[B+2];//各颜色子树有多少重颜色,及计数器
int up[maxr][B+2],c2[B+2];//各颜色父链有多少重颜色,及计数器
int c[maxn];//每点颜色
int dfn[maxn],out[maxn],tot;//dfn序
vector<int> df[maxr],ou[maxr];
// bool ED;
void dfs(int x)
{
for(int i=1;i<=cnt;i++) up[c[x]][i]+=c2[i];
if(a[c[x]])
{
c2[a[c[x]]]++;
c1[a[c[x]]]++;
}
dfn[x]=++tot;
df[c[x]].push_back(x);
for(int j=1;j<=cnt;j++) down[c[x]][j]-=c1[j];
for(int i:e[x])
{
dfs(i);
}
for(int j=1;j<=cnt;j++) down[c[x]][j]+=c1[j];
if(a[c[x]]) c2[a[c[x]]]--;
out[x]=tot;
ou[c[x]].push_back(x);
}
signed main()
{
// clock_t start=clock();
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
// freopen("P5901_26.in","r",stdin);
// freopen("P5901_26.out","w",stdout);
cin>>n>>r>>q;
cin>>c[1];
col[c[1]].push_back(1);
for(int i=2;i<=n;i++)
{
int op;
cin>>op;
e[op].push_back(i);
cin>>op;
c[i]=op;
col[op].push_back(i);
}
for(int i=1;i<=r;i++)
{
if(col[i].size()>B) a[i]=++cnt;
}
dfs(1);
while(q--)
{
int x,y;
cin>>x>>y;
if(a[y]) cout<<down[x][a[y]]<<endl;
else if(a[x]) cout<<up[y][a[x]]<<endl;
else
{
long long ret=0;
for(int i=0,j=0;i<(int)ou[x].size();i++)
{
while(j<(int)df[y].size()&&dfn[df[y][j]]<=out[ou[x][i]]) j++;
ret+=j;
}
for(int i=0,j=0;i<(int)df[x].size();i++)
{
while(j<(int)df[y].size()&&dfn[df[y][j]]<dfn[df[x][i]]) j++;
ret-=j;
}
cout<<ret<<endl;
}
}
// clock_t end=clock();
// cerr<<static_cast<double>(end-start)/CLOCKS_PER_SEC;
// cerr<<(&ED-&ST)/1024.0/1024.0;
return 0;
}

浙公网安备 33010602011771号