点分治是一个很有意思的东西。
一般可以用来静态地处理树上路径问题。
先看下题吧:
给定一棵有n个点的树
询问树上距离为k的点对是否存在。
怎么考虑这个问题呢?
我们不妨先任选一个点为根RT。
那么对于树上路径分成两种:跨过根RT的路径,在RT一颗子树内的路径(不经过RT)。
运用分治的思想考虑下第二种路径:
我们可以处理RT的子树,那么总有一个点为根时,它会成为第一种路径。
那么对于一个根,只处理处跨过它的路径,递归处理子树,
我们发现,这可以不重不漏的包含所有情况,且是具有相同子结构的。
然后以谁为根能够最优呢???
其实我们发现,这个递归的次数是与树的深度有关的。
所以当我们以这颗树的重心为根就能够保证只递归logN次。。。
接下来的问题就是,怎样求路径,且判断路径长度是否为k。
假设当前递归到了RT为根的子树。
有两种方法可以求这条路径:
①dfs一遍求出所有的点到根的距离,然后sort一遍,
两个指针从两边往中间扫,那么就可以开心的找出所有的等于k的路径O(∩_∩)O~~啦。。。
开心。。。
啦吗??
其实我们也很容易注意到:在一棵子树内为k的路径也会被统计进来。。
所以我们需要把它给判掉,很简单的。
②我们发现路径长度并不大,所以考虑开一个桶。
我们依次遍历每棵子树,遍历过程中计算答案,
即和之前已经遍历过了的子树计算贡献。
然后遍历完后把它的路径加入桶就好了。
这样既不重不漏,又不用担心来自一颗子树的问题,舒服。
所以,我们点分治模板就搞完了。。。。。。
1 #include<bits/stdc++.h> 2 #define RG register 3 #define IL inline 4 #define DB double 5 #define LL long long 6 using namespace std; 7 8 IL int gi() { 9 RG int x=0,w=0; char ch=0; 10 while (ch<'0'||ch>'9') {if (ch=='-') w=1;ch=getchar();} 11 while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar(); 12 return w?-x:x; 13 } 14 15 const int N=1e4+10; 16 const int MAX=1e7+10; 17 18 int jud[MAX],qur[N],ans[N]; 19 int num,a[N],b[N],dis[N]; 20 int n,m,RT,now,tot,head[N],vis[N],siz[N]; 21 22 IL bool cmp(int x,int y) {return dis[a[x]]<dis[a[y]];} 23 24 struct Edge{int next,to,ver;}e[N<<1]; 25 IL void make(int a,int b,int c) { 26 e[++tot]=(Edge){head[a],b,c},head[a]=tot; 27 e[++tot]=(Edge){head[b],a,c},head[b]=tot; 28 } 29 30 void Get_size(int x,int fx) { 31 RG int i,y; 32 for (i=head[x],siz[x]=1;i;i=e[i].next) { 33 if ((y=e[i].to)==fx) continue; 34 Get_size(y,x),siz[x]+=siz[y]; 35 } 36 } 37 38 void Get_root(int x,int fx,int S) { 39 RG int i,y,Mpr=S-siz[x]; 40 for (i=head[x];i;i=e[i].next) { 41 if ((y=e[i].to)==fx||vis[y]) continue; 42 Get_root(y,x,S),Mpr=max(Mpr,siz[y]); 43 } 44 if (Mpr<now) now=Mpr,RT=x; 45 } 46 47 void dfs(int x,int fx) { 48 RG int i,y; 49 a[x]=++num,b[num]=x; 50 for (i=1;i<=m;++i) 51 if (qur[i]>=dis[x]) ans[i]|=jud[qur[i]-dis[x]]; 52 for (i=head[x];i;i=e[i].next) { 53 if ((y=e[i].to)==fx||vis[y]) continue; 54 dis[y]=dis[x]+e[i].ver,dfs(y,x); 55 } 56 } 57 58 void solve(int x) { 59 RG int i,j,y; 60 Get_size(x,0),now=n,Get_root(x,0,siz[x]); 61 dis[RT]=0,jud[0]=1; 62 for (i=head[RT],vis[RT]=1;i;i=e[i].next) { 63 if (vis[y=e[i].to]) continue; 64 dis[y]=dis[RT]+e[i].ver,dfs(y,RT); 65 for (j=a[y];j<=num;++j) jud[dis[b[j]]]=1; 66 } 67 for (;num;--num) jud[dis[b[num]]]=0; 68 for (i=head[RT];i;i=e[i].next) 69 if (!vis[y=e[i].to]) solve(y); 70 } 71 72 int main () 73 { 74 RG int i,x,y,z; 75 n=gi(),m=gi(); 76 for (i=1;i<n;++i) 77 x=gi(),y=gi(),z=gi(),make(x,y,z); 78 for (i=1;i<=m;++i) qur[i]=gi(); 79 solve(1); 80 for (i=1;i<=m;++i) puts(ans[i]?"AYE":"NAY"); 81 return 0; 82 } 83 // size 必须每次重新求 因为每个点的子树有可能改变了 84 // 这样才能保证每次选的都是重心 否则很容易被链给卡掉
洛谷上这个题的数据是真的水,我叫了两遍错的都A了。
一定要仔细写好了,略略略别学了一个假的点分治~~~~(>_<)~~~~。
放几道入门题吧(作为初学者的我还没很多好题。。。):
一、Race
二、Distance in Tree
三、Tree
题解到时候放上。