点分治本质与模板
点分治
PS:零基础可以学习模板题的第一篇题解,此随笔仅做总结
本质:
利用基础的分治思想,类似线段树或者归并,只不过是转换到树上。每次需要在当前子树中找到一个点,这个点可以是重心,或者是满足"每个子树大小的最大值\(\leq tot/2\)"的某个点),选取这个点递归这个点的子树,可以保证递归\(logn\)层。
随后在每一层分类讨论如何维护每个子树归并的答案贡献(分治的主要思想),要用什么样的复杂度的算法。如果每层用\(O(n)\)的做法,总复杂度就是\(O(nlogn)\)的。每层用\(O(nlogn)\)的做法,总复杂度就是\(O(nlog^2n)\)的。
点分治给我的感觉其实就是个思想,类似启发式合并。
需要注意的点:
1.要标记一下重心被使用过,以免其余子树做操作的时候重复计算之前的重心。
2.注意讨论辅助数组是否在递归子树前是否需要清0。
模板题目:P3806 【模板】点分治1
Code:
#include<bits/stdc++.h>
using namespace std;
int n,m,k;
const int N=1e4+10;
struct node{
int to;
int next;
int dis;
}edge[N<<1];
int head[N],num_edge,u,v,_d,base;
bool st[N];
bool vis[100000007];
void edge_add(int from,int to,int dis){
edge[++num_edge].next=head[from];
edge[num_edge].to=to;
edge[num_edge].dis=dis;
head[from]=num_edge;
}
int get_size(int u,int fa){
if(st[u])return 0;
int res=1;
for(int i=head[u];i;i=edge[i].next){
int to=edge[i].to;
if(to==fa)continue;
res+=get_size(to,u);
}
return res;
}
#define debug(x) cout<<#x<<" :"<<x<<endl
const int M=200;
struct node1{
int val;
int id;
}ask[M];
int ans[M];
int p[N];
int q[N];
int conp=0;
int conq=0;
int get_wc(int u,int fa,int tot,int &wc){
if(st[u])return 0;
int mx=0;
int res=1;
for(int i=head[u];i;i=edge[i].next){
int to=edge[i].to;
if(to==fa)continue;
int val=get_wc(to,u,tot,wc);
mx=max(mx,val);
res+=val;
}
mx=max(mx,tot-res);
if(mx<=tot/2){
wc=u;
}
return res;
}
void get_dist(int u,int fa,int dist,int &con){
if(st[u])return ;
p[++con]=dist;
for(int i=head[u];i;i=edge[i].next){
int to=edge[i].to;
if(to==fa)continue;
get_dist(to,u,dist+edge[i].dis,con);
}
}
void cal(int u){
if(st[u])return ;
get_wc(u,-1,get_size(u,-1),u);
st[u]=true;
//两点在一个子树中 递归做
//一点在重心 一点在子树
//两点在不同子树中
conq=0;
for(int i=head[u];i;i=edge[i].next){
int to=edge[i].to;
get_dist(to,u,edge[i].dis,conp);
//枚举一个点在当前子树,另一个点是从前面所有子树的内容转移过来的
for(int j=1;j<=conp;++j){
for(int h=1;h<=m;++h){
if(ask[h].val>=p[j]&&(!ans[ask[h].id])){
if(vis[ask[h].val-p[j]]){
ans[ask[h].id]=1;
}
}
}
}
for(int j=1;j<=conp;++j){//一个点在重心 一个点在该子树
vis[p[j]]=1;
q[++conq]=p[j];
}
conp=0;
}
for(int i=1;i<=conq;++i){
vis[q[i]]=false;//重置
}
for(int i=head[u];i;i=edge[i].next)cal(edge[i].to);
}
int main(){
scanf("%d %d",&n,&m);
for(int i=1;i<=n-1;++i){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
edge_add(u,v,w);
edge_add(v,u,w);
}
vis[0]=true;
for(int i=1;i<=m;++i){
scanf("%d",&ask[i].val);
ask[i].id=i;
}
cal(1);
for(int i=1;i<=m;++i){
if(ans[ask[i].id])puts("AYE");
else puts("NAY");
}
return 0;
}