放在开头的话
1.本人1月13日才刚刚接触了淀粉汁,所以可能很多东西没有理解完全甚至出错,如果您有什么疑问的话,请发在下面的评论区。
2.淀粉汁是跟着一篇题解学的,因此可以有些代码很像,但是原来题解的部分没讲到的地方我可能会提出来
3.这东西目前可能会更的很慢
初级
什么是点分治?
点分治是处理树上问题的一种高效的办法,时间复杂度很优秀,而且思想比较巧妙。
怎么来做呢?
首先我们引入树的重心
树的重心
树的重心也叫树的质心。找到一个点,其所有的子树中最大的子树节点数最少,那么这个点就是这棵树的重心,删去重心后,生成的多棵树尽可能平衡。
什么意思呢?
我们可以从这里推得以树的重心为根的任意一颗子树大小不超过n/2
证明就不用了吧
于是我们求log2N次重心,那么每个点就能被确定了。
于是时间复杂度就变成了O(NlogN)
怎么求呢?
根据它的定义,树的重心一定是最大子树最小的点。
感性理解即可
于是照着求就可以了啊。
但是我们要多次求,因此我们得加一个条件,判断是否可以访问
vis[]
或者fw[]
就可以了
于是整个代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| inline void getzx(int t,int fat) { int i,j; sz[t]=1; maxp[t]=0; for(i=head[t];i;i=e[i].next) { j=e[i].to; if(j==fat||vis[j]) continue; getzx(j,t); sz[t]+=sz[j]; maxp[t]=max(sz[j],maxp[t]); } maxp[t]=max(maxp[t],tot-sz[t]); if(maxp[t]<maxp[rt]) rt=t; }
|
点分治怎么做
我们只需要不断寻找重心,用这些重心来计算我们要的答案
我们举个例子:
现在这里有一棵树
(假定蓝色是未被处理的点,红色是当前子树的重心,绿色是处理完的点)

先找到全局重心

然后对他的子树,也这样找

一直接下去

然后做完了。
等一下,这不是大部分点都被扫过了吗,时间复杂度怎么还会是O(NlogN)?
的确,这张图中大部分店都被扫到了,而且都求了重心,但是如果每条边中间连接1000个点,只有m,i,h三条边被处理的重心增多了。
增多了多少呢?
每条边在10左右。
现在你感性理解到了复杂度了吧
由于之前有一个推论
我们可以从这里推得以树的重心为根的任意一颗子树大小不超过n/2
每次树的大小会变得不超过原来的一般,log2N次后树的大小就会变成1,每一次最多遍历整棵树O(N),复杂度就得到O(NlogN)?了
不信?我们用随机几十组数据看看

数据生成器放在这里
测试用的代码放在这里 改自P2634 [国家集训队]聪聪可可
下面是一个模板代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| inline void solve(int t) { int i,j; vis[t]=judge[0]=1; calc(t); for(i=head[t];i;i=e[i].next) { j=e[i].to; if(vis[j]) continue; tot=sz[j]; maxp[rt=0]=bign; getzx(j,0); solve(rt); } }
|
例题
P3806 【模板】点分治1
大意
给定一棵有n个点的树
询问树上距离为k的点对是否存在。
其实是很无脑的两个桶,一个judge[]
和一个tmp[]
,judge
存非本路径上长度能达到的数字,tmp[]
存本路径上能达到的数字,把此时重心看做LCA,于是若tmp[]中一个数+judge[]一个数=k,就说明路径存在。
接着,这条路径遍历完后,把tmp的数字转移到judge里面
这个地方我们找完了,只需要把judge里面的数字清空即可。
于是我们就有代码了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
| #include<iostream> #include<cmath> #include<cstdio> #include<cstring> #include<queue> #include<stack> #include<vector> #include<set> #include<map> #include<algorithm>
using namespace std;
const int maxn=10010,bign=10001000;
int n,m,tmp[bign],judge[bign]; int sz[maxn],vis[maxn]; int head[maxn],que[maxn]; int size,maxp[maxn]; int tot,rt,dis[maxn]; int q[bign],ynn[maxn];
struct edge{ int next,to,dis; }e[maxn*2];
inline void addedge(int next,int to,int dis) { e[++size].to=to; e[size].dis=dis; e[size].next=head[next]; head[next]=size; }
inline void getzx(int t,int fat) { int i,j; sz[t]=1; maxp[t]=0; for(i=head[t];i;i=e[i].next) { j=e[i].to; if(j==fat||vis[j]) continue; getzx(j,t); sz[t]+=sz[j]; maxp[t]=max(sz[j],maxp[t]); } maxp[t]=max(maxp[t],tot-sz[t]); if(maxp[t]<maxp[rt]) rt=t; }
inline void getdis(int t,int fat) { tmp[++tmp[0]]=dis[t]; int i,j,k; for(i=head[t];i;i=e[i].next) { j=e[i].to; k=e[i].dis; if(vis[j]||j==fat) continue; dis[j]=dis[t]+k; getdis(j,t); } }
inline void calc(int t) { int p=0,i,j,k,l; for(i=head[t];i;i=e[i].next) { j=e[i].to; k=e[i].dis; if(vis[j]) continue; tmp[0]=0; dis[j]=k; getdis(j,t); for(k=tmp[0];k;k--) for(l=1;l<=m;l++) if(que[l]>=tmp[k]) ynn[l]|=judge[que[l]-tmp[k]]; for(k=tmp[0];k;k--) q[++p]=tmp[k],judge[tmp[k]]=1; } for(i=p;i;i--) judge[q[i]]=0; }
inline void solve(int t) { int i,j; vis[t]=judge[0]=1; calc(t); for(i=head[t];i;i=e[i].next) { j=e[i].to; if(vis[j]) continue; tot=sz[j]; maxp[rt=0]=bign; getzx(j,0); solve(rt); } }
int main() { ios::sync_with_stdio(false); register int i,h; int t1,t2,t3; cin>>n>>m; for(i=1;i<n;i++) { cin>>t1>>t2>>t3; addedge(t1,t2,t3); addedge(t2,t1,t3); } for(i=1;i<=m;i++) cin>>que[i]; maxp[rt=0]=n; tot=n; getzx(1,0); solve(rt); for(i=1;i<=m;i++) { if(ynn[i]) cout<<"AYE"<<endl; else cout<<"NAY"<<endl; } return 0; }
|