apple365的分治合集!

目录

  1. 根号分治
  2. 点分治
  3. CDQ 分治
  4. 待补

正文

根号分治

其实分块也是一种根号分治。
本质是将一组询问按照某个值域来划分(通常取根号),不超过 \(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 分治

使用场景:

  1. 三维偏序问题
  2. 优化 dp
  3. 将动态问题转化为静态问题

算法原理:
第一维排序,第二维归并,第三维树状数组。

板子(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 为值域)。

posted @ 2023-02-18 19:26  Forever1507  阅读(45)  评论(0编辑  收藏  举报