BZOJ 1999: [Noip2007]Core树网的核

题目大意

求无根树的直径上一段长度不超过 s 的链,使得最远的点到该链的距离最小。输出这个最小距离。

5 n 500000 ,   0 s 2 31


一棵树的所有直径必有公共交集的证明见这里

性质:

  • 对于树中的任意一点,距离其最远的点一定是树的直径的某一端点。

  • 所有的直径是等价的,即任意一条所能求出的最小偏心距相等。(因为所有直径必有公共交集,且去除这个交集部分后,各直径在同一边的剩余部分长度相等,如下图)

图中 a e c b e d 是两条直径, e 是它们的交集,那么 a = b ,   c = d

f c 上节点的一个子树,由于 a e g f 不是直径,那么有 g + f < c ,如果从直径 a e c 上选 s ,那么要计算到 d ,如果从直径 b e d 上选 s ,那么要计算到 c ,而 c = d ,所以从任意一条直径上选取 s ,结果没有影响。

那么可以两遍 d f s 求出一条直径,如下图直线所示:

图中每个三角形表示直径上的点的子树,我们可以 d f s 预处理出每个三角形的大小,由于每个点只会被访问一次,所以是 O ( n ) 的。

显然选取的链越长越优,因此可以在直径上枚举 l , r ,其中 r l + 1 = s ,每次对 [ l , r ] 上的三角形大小、 l 到直径左端点的距离、 r 到直径右端点的距离取 m a x ,然后对所有的答案取 m i n

总复杂度 O ( n )


诶呀呀不能用dfs呀,500000爆栈了呀QAQ

#include <cstdio>
#include <vector>
#include <queue>

const int N = 500005, INF = 0x7fffffff;

struct Edge {
    int u, v, c;
};
std::vector<Edge> edges;
std::vector<int> G[N];
std::queue<int> Q;

int pre[N], node[N], len, val[N], L[N], R[N], dis[N], S, T, mx;
bool vis[N];

int read() {
    int x = 0; char c = getchar();
    while (c < '0' || c > '9') c = getchar();
    while (c >= '0' && c <= '9') {
        x = (x << 3) + (x << 1) + (c ^ 48);
        c = getchar();
    }
    return x;
}
int max(int x, int y) {
    if (x >= y) return x; return y;
}
int min(int x, int y) {
    if (x <= y) return x; return y;
}
void add_edge(int u, int v, int c) {
    G[u].push_back(edges.size());
    edges.push_back((Edge){u, v, c});
}

void bfs1(int s) {
    Q.push(s); pre[s] = dis[s] = 0;
    while (!Q.empty()) {
        int u = Q.front(); Q.pop();
        int sz = G[u].size();
        for (int i = 0; i < sz; ++i) {
            Edge e = edges[G[u][i]];
            if (e.v != pre[u]) {
                Q.push(e.v); pre[e.v] = u;
                dis[e.v] = dis[u] + e.c;
            }
        }
        if (dis[u] > mx) mx = dis[u], T = u;
    }
}
void bfs2(int s) {
    Q.push(s); pre[s] = dis[s] = 0;
    while (!Q.empty()) {
        int u = Q.front(); Q.pop();
        int sz = G[u].size();
        for (int i = 0; i < sz; ++i) {
            Edge e = edges[G[u][i]];
            if (!vis[e.v] && e.v != pre[u]) {
                Q.push(e.v);
                dis[e.v] = dis[u] + e.c;
            }
        }
        val[s] = max(val[s], dis[u]);
    }
}

int main() {
    int n = read(), s = read();
    for (int i = 1; i < n; ++i) {
        int u = read(), v = read(), c = read();
        add_edge(u, v, c); add_edge(v, u, c);
    }
    bfs1(1); S = T, mx = 0; bfs1(S);                    //找出一条直径+计算直径上每个点到两端的距离 
    while (T) node[++len] = T, R[T] = dis[T], T = pre[T];
    for (int i = 2; i <= len; ++i) L[node[i]] = L[node[i-1]] + R[node[i-1]] - R[node[i]];

    for (int i = 1; i <= len; ++i) vis[node[i]] = 1;
    for (int i = 1; i <= len; ++i) bfs2(node[i]);       //递归直径上每个点的子树大小 

    int r = 1, ans = INF, mx = val[1];
    for (int l = 1; l <= len; ++l) {                    //滑动窗口 
        while (r + 1 <= len && L[node[r+1]] - L[node[l]] <= s) mx = max(mx, val[node[++r]]);
        ans = min(ans, max(mx, max(L[node[l]], R[node[r]])));
        if (r == len) break;
    }
    printf("%d\n", ans);

    return 0;
}

猜你喜欢

转载自blog.csdn.net/Milkyyyyy/article/details/81840675