apple365的分治合集!
目录
- 根号分治
- 点分治
- CDQ 分治
- 待补
正文
根号分治
其实分块也是一种根号分治。
本质是将一组询问按照某个值域来划分(通常取根号),不超过 \(X\) 时采用一种做法,超过了换另一种(一般一种是暴力,另外一个空间换时间或采用其他一些的算法结合)。
例:洛谷 P3396
首先显然有一个 for(int i=y;i<=n;i+=x)
的暴力。这个暴力的运行时间取决于 \(x\) 的大小。所以注意到当 \(x\) 大于或等于 \(\sqrt{n}\) 的时候可以实现 \(O(n\sqrt{n})\) 的优秀复杂度。
小于的话预处理一下空间换时间即可。
当然目前为止没有修改。
修改只要把预处理的那一部分把与修改的地方相关的(约 \(\sqrt{n}\) 个)改掉,其他部分依托依靠暴力就可以了。
然后这个题就被秒掉了。
查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int ans[1005][1005],n,m,a[150005],base;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
base=sqrt(n)+1;
for(int i=1;i<=n;++i){
cin>>a[i];
for(int j=1;j<=base;++j)ans[j][i%j]+=a[i];
}
for(int i=1;i<=m;++i){
char c;
int x,y;
cin>>c>>x>>y;
if(c=='A'){
if(x<=base){
cout<<ans[x][y]<<'\n';
}
else{
int sum=0;
for(int j=y;j<=n;j+=x)sum+=a[j];
cout<<sum<<'\n';
}
}
else{
for(int j=1;j<=base;++j)ans[j][x%j]-=a[x];
a[x]=y;
for(int j=1;j<=base;++j)ans[j][x%j]+=a[x];
}
}
return 0;
}
点分治
使用场景:树上大量路径统计问题。
算法原理:
按照重心划分树,可以使划分的时间复杂度 \(\Theta(\log n))\)。分治的思想在于,讨论这颗子树时经过或不经过根节点。
一个板子的代码(CF161D)
#include<bits/stdc++.h>
using namespace std;
int N,K,n,k,tmp[50005],lens[50005],ctr,siz[50005],cnt;
vector<int>nbr[50005];
bool del[50005];
void dfs(int cur,int fa){
siz[cur]=1;
int maxn=0;
for(auto to:nbr[cur]){
if(to==fa||del[to])continue;
dfs(to,cur);
if(ctr!=-1)return;
siz[cur]+=siz[to];
maxn=max(maxn,siz[to]);
}
maxn=max(maxn,n-siz[cur]);
if(maxn<=n/2){
ctr=cur;
siz[fa]=n-siz[cur];
}
return;
}
long long ans;
void dfs2(int cur,int fa,int len){
if(len>k)return;
ans+=lens[k-len]+(len==k);
tmp[++cnt]=len;
for(auto to:nbr[cur]){
if(to==fa||del[to])continue;
dfs2(to,cur,len+1);
}
return;
}
void solve(int cur){
for(auto to:nbr[cur]){
if(del[to])continue;
dfs2(to,cur,1);
for(int i=1;i<=cnt;++i)lens[tmp[i]]++;
cnt=0;
}
memset(lens,0,sizeof(lens));
del[cur]=1;
for(auto to:nbr[cur]){
if(del[to])continue;
n=siz[to],k=K;
ctr=-1;
dfs(to,cur);
solve(ctr);
}
return;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>N>>K;
n=N,k=K;
for(int i=1;i<n;++i){
int u,v;
cin>>u>>v;
nbr[u].push_back(v);
nbr[v].push_back(u);
}
ctr=-1;
dfs(1,0);
solve(ctr);
cout<<ans;
return 0;
}
CDQ 分治
使用场景:
- 三维偏序问题
- 优化 dp
- 将动态问题转化为静态问题
算法原理:
第一维排序,第二维归并,第三维树状数组。
板子(P3810)
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,k,ans[200005];
map<int,map<int,map<int,int> > >mp;
struct node{
int x,y,z,s,res;
}tmp[500005],a[200005];
int tree[200005]={};
int lowbit(int x){
return x&-x;
}
int query(int x){
int sum=0;
for(int i=x;i>0;i-=lowbit(i))sum+=tree[i];
return sum;
}
void update(int x,int val){
for(int i=x;i<=k;i+=lowbit(i))tree[i]+=val;
}
void merge_sort(int lt,int rt){
if(lt==rt)return;
int mid=lt+rt>>1;
merge_sort(lt,mid);
merge_sort(mid+1,rt);
int i=lt,j=mid+1,p=lt;
while(i<=mid&&j<=rt){
if(a[i].y>a[j].y){
a[j].res+=query(a[j].z);
tmp[p++]=a[j++];
}
else update(a[i].z,a[i].s),tmp[p++]=a[i++];
}
while(i<=mid)update(a[i].z,a[i].s),tmp[p++]=a[i++];
while(j<=rt)a[j].res+=query(a[j].z),tmp[p++]=a[j++];
for(int i=lt;i<=mid;++i)update(a[i].z,-a[i].s);
for(int k=lt;k<=rt;++k)a[k]=tmp[k];
return;
}
bool cmp(node q,node w){
if(q.x==w.x&&q.y==w.y)return q.z<w.z;
else if(q.x==w.x)return q.y<w.y;
return q.x<w.x;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>k;
for(int i=1;i<=n;++i){
cin>>a[i].x>>a[i].y>>a[i].z;
a[i].s=1;
a[i].res=0;
}
sort(a+1,a+1+n,cmp);
int cnt=1;
for(int i=2;i<=n;++i){
if(a[i].x==a[cnt].x&&a[i].y==a[cnt].y&&a[i].z==a[cnt].z){
a[cnt].s++;
}
else a[++cnt]=a[i];
}
merge_sort(1,cnt);
for(int i=1;i<=cnt;++i)ans[a[i].res+a[i].s-1]+=a[i].s;
for(int i=0;i<n;++i)cout<<ans[i]<<'\n';
}
时间复杂度:\(\Theta(n \log n \log c)\)(c 为值域)。