算法学习:点分治
【定义】
【树的重心】所有子树的大小都不超过整个树大小的一半的点
【重心的求取】依次查找每个点,当此点的最大子树最小时肯定是重心
【解决问题】
处理树上路径信息
类似这样:
一棵树内n个点,求距离等于k的点对个数
一棵树内n个点,距离等于质数的点的对数
一棵树内n个点,是否存在距离等于k的点
.....................................
【解决思路】
直接莽,n^3的复杂度,肯定炸
所以我们需要想一种方法,能够显著减少其复杂度,于是就用到了分治
选取一个节点作为将这个DAG的根,使其变成一颗树,
想象一手拿着根,提溜着抖搂一下然后就成了一颗树,下面有若干颗子树
于是距离符合要求的情况有两种,
第一种:两个点都在同一颗子树中
第二种:两个点在不同子树中,但是很明显他们都经过了根节点
所以我们计算出各个点到根节点的距离,然后排序比较一番就能够在nlogn的时间内求得答案,就能够解决第二种情况的问题
而第一种情况,我们再向下继续进行找根变成树的操作,他们两个也会在不同的子树中
这样子,第一种情况就会变成第二种,我们也就只需要考虑如何处理第二种和如何把第一种变成第二种
第一种变成第二种的关键在于这个根的选取,如果选的根不对很有可能选出来一个链,然后就O(n)处理,果断GG
怎么分治才能使层数尽量小,使两边尽量均衡,达到二分的效果的情况下层数更少,能够缩减至logn,所以我们需要取重心做,
这个根是重心,所以下面的子树的层数会是最少的
#include<map> #include<cstdio> #include<iostream> #include<algorithm> using namespace std; const int MAXN = 10010; const int MAXM = 20010; const int MAXK = 10000010; struct node { int to; int val; int nt; }edge[MAXM]; int st[MAXN], ask[110],top; int siz[MAXN], f[MAXN], root; int dis[MAXN], a[MAXN], S,cnt,n,m; int ans[MAXK]; bool vis[MAXN]; void add(int x,int y,int z) { top++; edge[top] = { y,z,st[x] }; st[x] = top; } void get_root(int x, int fa) { siz[x] = 1, f[x] = 0; for (int i = st[x]; i; i = edge[i].nt) { int to = edge[i].to; if (to == fa || vis[to]) continue; get_root(to, x); f[x] = max(f[x], siz[to]); siz[x] += siz[to]; } f[x] = max(f[x], S - siz[x]); if (f[x] < f[root]) { root = x; } } void get_dis(int x, int fa,int d) { dis[++cnt] = a[x]; for (int i = st[x]; i; i = edge[i].nt) { int to = edge[i].to; if (vis[to] || to == fa) continue; a[to] = d + edge[i].val; get_dis(to, x,a[to]); } } void solve(int x,int len,int w) // { cnt = 0; a[x] = len; get_dis(x, 0, len); for(int i=1;i<=cnt;i++) for (int j = 1; j <= cnt; j++) { if (i != j && dis[i]+dis[j]<MAXK) ans[dis[i] + dis[j]] += w; } } void divide(int x) { solve(x, 0, 1); vis[x] = 1; for (int i = st[x]; i; i = edge[i].nt) { int to = edge[i].to; if (vis[to]) continue; solve(to, edge[i].val, -1); S = siz[x], root = 0, f[0] = n; get_root(to, x); divide(root); } } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n-1; i++) { int x, y, z; scanf("%d%d%d", &x, &y, &z); add(x, y, z); add(y, x, z); } siz[0] = 0,f[0] = n, root = 0,S=n; get_root(1, 0); divide(root); for (int i = 1; i <= m; i++) { int k; scanf("%d", &k); if (ans[k]) printf("AYE\n"); else printf("NAY\n"); } return 0; }