【洛谷 P4886】 快递员 (点分治)

这题因为一些小细节还是(debug)了很久。。。不过我第一次用脚本对拍,不亏。

先随便找一个点作为根,算出答案,即所有点对到这个点的距离和的最大值,并记录所有距离最大的点对。如果这个点在任意一个距离最大的点对之间的路径上,那么答案显然不能再优了,因为这个点对的答案是不能减小了的。如果有两个距离最大的点对不在根的同一子树中,答案也是显然不能再优了的,因为一个点对答案减小的同时,另一个会增大。只有当所有距离最大的点对在根的同一子树中,这时更优答案可能在这个子树里,向这个子树递归处理就行了。为什么说可能?因为往这个子树走的同时可能会存在在另一个子树中原本不是距离最大的点对变为距离最大的点对,所以我们要一直对答案取最小值。直接走最多会走(n)次,而如果我们每次都走子树的重心,最多走(logN)次,所以总时间复杂度(O(mlog n))

现在讲讲具体怎么实现,主要就难在怎么判断根在不在两个点之间的路径上。求(LCA)是最简单粗暴的方法,但时间复杂度要多一个(log),其实类似于点分治里的统计,只需要看这个点对的两个点在不在同一子树里就行了,若在,则路径不经过根,反之亦然。

然后,看(Code)吧。

#include <iostream>
#include <cstdio>
#define INF 2147483647
using namespace std;
inline int read(){
	int s = 0, w = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9') { if(ch == '-') w = -1; ch = getchar(); }
	while(ch >= '0' && ch <= '9') { s = s * 10 + ch - '0'; ch = getchar(); }
	return s * w;
}
const int MAXN = 100010;
struct Edge{
	int next, to, dis;
}e[MAXN << 1];
int head[MAXN], num, x[MAXN], y[MAXN], vis[MAXN], maxson[MAXN], p[MAXN], q[MAXN], belong[MAXN], deep[MAXN], size[MAXN];
int n, m, root, Max, ans = 2147483647;
inline void Add(int from, int to, int dis){
	e[++num].to = to;
	e[num].dis = dis;
	e[num].next = head[from];
	head[from] = num;
}
void getRoot(int u,int fa,int ALL){   //找重心
    size[u] = 1; maxson[u] = 0;
    for(int i = head[u]; i; i = e[i].next)
       if(e[i].to != fa && !vis[e[i].to]){
 	     getRoot(e[i].to, u, ALL);
	     size[u] += size[e[i].to];
         maxson[u] = max(maxson[u], size[e[i].to]);
       }
    maxson[u] = max(maxson[u], ALL - size[u]);
    if(maxson[u] < Max) root = u, Max = maxson[u];
}
void dfs(int u, int fa, int dep, int rt){ //算出每个点的深度,并标记属于根的哪棵子树
    belong[u] = rt;
    deep[u] = dep;
    for(int i = head[u]; i; i = e[i].next)
       if(e[i].to != fa)
         dfs(e[i].to, u, dep + e[i].dis, rt);
}
void Solve(int u){
    if(vis[u]){
      printf("%d
", ans);
      exit(0);
    }      
    vis[u] = 1;
    for(int i = head[u]; i; i = e[i].next)
       dfs(e[i].to, u, e[i].dis, e[i].to);
    deep[u] = 0;
    Max = 0; p[0] = 0;
    int last = 0;
    for(int i = 1; i <= m; ++i)    //找出所有距离最大的点对并求出答案
       if(deep[x[i]] + deep[y[i]] > Max) 
         Max = deep[x[i]] + deep[y[i]], p[p[0] = 1] = i;
       else if(deep[x[i]] + deep[y[i]] == Max)
         p[++p[0]] = i;
    ans = min(ans, Max);   //更新答案
    for(int i = 1; i <= p[0]; ++i){
       if(belong[x[p[i]]] != belong[y[p[i]]]){  //如果有一个点对之间的路径经过根,当前答案一定是最优的
         printf("%d
", ans);
         exit(0);
       }
       else 
         if(!last) last = belong[x[p[i]]];
         else if(last != belong[x[p[i]]]){  //如果两个点对不在同一子树里,当前答案也一定最优
           printf("%d
", ans);
           exit(0);
         }
    }
    Max = 9996666;
    getRoot(last, u, size[last]);  //找子树的重心
    Solve(root);   //递归处理
}
int a, b, c;
int main(){
    n = read(); m = read();
    for(int i = 2; i <= n; ++i){
       a = read(); b = read(); c = read(); 
       Add(a, b, c); Add(b, a, c);
    }
    for(int i = 1; i <= m; ++i){
       x[i] = read(); y[i] = read();
    }
    Max = 9996666; getRoot(1, 0, n);
    Solve(root);
    return 0;
}

原文地址:https://www.cnblogs.com/Qihoo360/p/9675522.html