POJ - 1741 Tree模板题
就是求树上距离小于等于k的点对有多少对.
就是树的点分治模板题, 推荐ioi国家集训队论文, 里面讲解的非常清楚了.
将无根树转化成有根树进行观察。满足条件的点对有两种情况:两个点的路径横跨树根,两个点位于同一颗子树中。
如果我们已经知道了此时所有点到根的距离a[i],a[x] + a[y] <= k的(x, y)对数就是结果,这个可以通过排序之后O(n)的复杂度求出。然后根据分治的思想,分别对所有的儿子求一遍即可,但是这会出现重复的——当前情况下两个点位于一颗子树中,那么应该将其减掉(显然这两个点是满足题意的,为什么减掉呢?因为在对子树进行求解的时候,会重新计算)。
在进行分治时,为了避免树退化成一条链而导致时间复杂度变为O(N^2),每次都找树的重心,这样,所有的子树规模就会变的很小了。时间复杂度O(Nlog^2N)。
树的重心的算法可以线性求解。
//树的点分治,该代码是求树上有多少对点的dis<=k
//vis代表该点是否已经当做过重心,siz是子树节点个数、mv是子树中最大的节点数
int n, cnt, head[maxn], k, vis[maxn], root, maxx, dis[maxn];
int ans, num, tot, siz[maxn], mv[maxn]; //tot代表当前树的节点个数、如果遇到时间过长,考虑重心是否找对
struct node {
int to, w, next;
} e[maxn<<1];
void add(int u, int v, int w) {
e[cnt] = (node){v, w ,head[u]};
head[u] = cnt++;
}
void getroot(int u, int fa) {
siz[u] = 1, mv[u] = 0;
for (int i = head[u]; ~i; i = e[i].next) {
int to = e[i].to;
if (to == fa || vis[to]) continue;
getroot(to, u);
siz[u] += siz[to];
mv[u] = max(mv[u], siz[to]);
}
mv[u] = max(mv[u], tot - siz[u]);
if (mv[u] < mv[root]) root = u;
}
void getdis(int u,int fa,int dep) {
dis[++num] = dep;
for (int i = head[u]; ~i; i = e[i].next) {
int to = e[i].to;
if (to == fa || vis[to]) continue;
getdis(to, u, dep + e[i].w);
}
}
int cal(int u,int f) {
int res = 0;
num = 0;
getdis(u,-1,f);
sort(dis+1,dis+num+1);
int r = num ;
for(int l = 1; l < r; l++) {
while(dis[l] + dis[r] > k && l < r)
r--;
res += r - l ;
}
return res;
}
void work(int u) {
vis[u] = 1;
ans += cal(u, 0);
for (int i = head[u]; ~i; i = e[i].next) {
int to = e[i].to;
if (vis[to]) continue;
int tmp = cal(to, e[i].w);
//传e[i].w是因为这是根节点到该点的距离,因为要和k判断关系,所以必须加上
ans -= tmp;
mv[root=0] = tot = siz[to];
getroot(to, -1);
work(root);
}
}
void solve() {
while(~scanf("%d%d",&n,&k)) {
if (n + k == 0 ) break;
cnt = 0; Fill(head, -1); Fill(vis, 0);
for (int i = 1; i < n; i++) {
int u, v, w;
scanf("%d%d%d",&u,&v,&w);
add(u, v, w); add(v, u, w);
}
ans = 0 ;
mv[root=0] = tot = n;
getroot(1, -1);
work(root);
cout << ans << endl;
}
}