点分治能够快速处理树上路径问题。
在考虑路径问题时,可以利用分治的思想。选出一个根节点,先考虑所有经过根节点的路径。路径的起点和终点分别在根节点的两个子树上。解决之后,要解决的就是各个子树的路径问题了。我们再找出每个子树的根节点,然后一样递归处理即可。
问题在于应该选哪个节点作为子树的根节点。最优方法是选取树的重心。因为重心有一个性质,它的各个子树的大小都不超过总大小的一半。这样每一次都分为若干个不超过一半的子问题,就变成了分治算法。分治次数为$O( log n)$。如果想要达成$O(n log n)$的复杂度,那对于每个节点的计算就要$O(n)$完成
Code
有一些细节需要注意。
/*DennyQi 2019*/ #include <cstdio> #include <algorithm> #include <cstring> #include <queue> using namespace std; const int N = 10010; inline int read(){ int x(0),w(1); char c = getchar(); while(c^'-' && (c<'0' || c>'9')) c = getchar(); if(c=='-') w = -1, c = getchar(); while(c>='0' && c<='9') x = (x<<3)+(x<<1)+c-'0', c = getchar(); return x*w; } int n,m,x,y,z,S,rt,cnt,test[N],vis[N],sz[N],maxp[N],judge[10000010],rem[N],q[N],Q[N]; int head[N],nxt[N<<1],to[N<<1],cost[N<<1],cnte; inline void add(int u, int v, int w){ to[++cnte] = v; cost[cnte] = w; nxt[cnte] = head[u]; head[u] = cnte; } void getrt(int u, int Fa){ sz[u] = 1; maxp[u] = 0; for(int i = head[u]; i; i = nxt[i]){ if(to[i] == Fa) continue; if(vis[to[i]]) continue; getrt(to[i],u); sz[u] += sz[to[i]]; maxp[u] = max(maxp[u],sz[to[i]]); } maxp[u] = max(maxp[u],S-sz[u]); if(maxp[u] < maxp[rt]) rt = u; } void getdis(int u, int Fa, int d){ rem[++cnt] = d; for(int i = head[u]; i; i = nxt[i]){ if(to[i] == Fa) continue; if(vis[to[i]]) continue; getdis(to[i],u,d+cost[i]); } } inline void calc(int u){ int p = 0; for(int i = head[u]; i; i = nxt[i]){ if(vis[to[i]]) continue; cnt = 0; getdis(to[i],u,cost[i]); for(int j = 1; j <= cnt; ++j){ for(int k = 1; k <= m; ++k){ if(Q[k] >= rem[j]){ test[k] |= judge[Q[k]-rem[j]]; } } } for(int j = 1; j <= cnt; ++j){ q[++p] = rem[j]; judge[rem[j]] = 1; } } for(int i = 1; i <= p; ++i) judge[q[i]] = 0; } void solve(int u){ vis[u] = 1; judge[0] = 1; calc(u); for(int i = head[u]; i; i = nxt[i]){ if(vis[to[i]]) continue; S = sz[to[i]]; maxp[rt = 0] = 0x3f3f3f3f; getrt(to[i],-1); solve(rt); } } int main(){ // freopen("file.in","r",stdin); n = read(), m = read(); for(int i = 1; i < n; ++i){ x = read(), y = read(), z = read(); add(x,y,z); add(y,x,z); } for(int i = 1; i <= m; ++i) Q[i] = read(); S = n; maxp[rt] = 0x3f3f3f3f; getrt(1,-1); solve(rt); for(int i = 1; i <= m; ++i){ if(test[i]) printf("AYE "); else printf("NAY "); } return 0; }
带权树上长度为K的路径是否存在(洛谷P3806):点分治时记录每个根节点出发能达到的距离,开桶记录即可。
带权树上长度为3的倍数的路径条数(洛谷P2634):只需要记录一下模三的剩余系中的路径条数就好了。
带权树上长度为K的路径的最小边数(洛谷P4149):只需要维护的距离的时候多维护一个边数就好了。
带权树上长度$leq$K的路径条数(洛谷P4178):暴力统计前缀和会被菊花图卡爆。用树状数组维护,多一只$mathrm{log}$。