Luogu 3761 [TJOI2017]城市
BZOJ 4890。
在树上断开一条边之后会形成两个联通块,如果要使这一条边接回去之后保持一棵树的形态,那么必须在两个联通块之间各找一个点连接。
那么,对于每一条可能断开的边,它产生的答案是以下两者的最大值:
1、其中一个联通块的直径
2、两个联通块中从一个点出发最短的最长距离之和再加上这一条边的权值。
这么拗口,就叫做半径好了。
断开的边是肯定要枚举的,这题的主要矛盾变成了如何求这个半径。
思考一个点的半径会产生于哪里,有以下两种来源:
1、它子树中的点到它距离的最大值。
2、除了它的子树中的点,其他所有结点到它父亲结点的距离的最大值加上它到它父亲结点的距离。
其实再细分下去,这个“其他所有结点到它父亲结点的距离的最大值”又有两种来源:
1、它爷爷的最大值加上它爷爷到它父亲的距离
2、它父亲到它的某个子树中的点的最大值。
考虑到第二条有可能包含了它,所以搜的时候需要维护三个值:它子树中的点到它距离的最大值$fir_x$,以及它子树中的点到它距离的次大值$sec_x$,以及它去到子树最大值的儿子$maxp_x$。
要注意选择维护$maxp_x$是考虑到含有多条相同链长的情况。
假设$f_{fa}$是父亲到$x$距离的最大值。
我们在往下走的时候,如果$x == maxp_x$那么用$max(f_{fa}, sec_x) + dis(x, son)$更新$f_x$,否则用$max(f_{fa}, fir_x) + dis(x, son)$来更新$f_x$。
每搜到一个点,用$max(fir_x, f_{fa})$更新答案。
其实直径就是$max(fir_x + sec_x)$,搜的时候顺便算一下就好了。
然后还有个优化,就是只有当原来这棵树上的直径的边删掉重构之后才有可能影响答案,因为如果拿出一条直径以外的边重构,直径仍然存在。这样子就可以缩减一些外层循环的计算量。(似乎有整棵树是一条链的极端数据)。
时间复杂度$O(n^2)$,常数迷人。
Code:
#include <cstdio> #include <cstring> using namespace std; const int N = 5005; const int inf = 1 << 30; int n, tot = 1, head[N], dis[N], maxp[N]; int res, len = 0, toEdge[N], dia[N], fir[N], sec[N]; struct Edge { int to, nxt, val; bool lock; } e[N << 1]; inline void add(int from, int to, int val) { e[++tot].to = to; e[tot].val = val; e[tot].lock = 0; e[tot].nxt = head[from]; head[from] = tot; } inline void read(int &X) { X = 0; char ch = 0; int op = 1; for(; ch > '9' || ch < '0'; ch = getchar()) if(ch == '-') op = -1; for(; ch >= '0' && ch <= '9'; ch = getchar()) X = (X << 3) + (X << 1) + ch - 48; X *= op; } void dfs(int x, int fat, int nowDis, int inEdge) { dis[x] = nowDis, toEdge[x] = inEdge; for(int i = head[x]; i; i = e[i].nxt) { int y = e[i].to; if(y == fat) continue; dfs(y, x, nowDis + e[i].val, i); } } inline void chkMax(int &x, int y) { if(y > x) x = y; } inline void chkMin(int &x, int y) { if(y < x) x = y; } inline int max(int x, int y) { return x > y ? x : y; } void dp(int x, int fat) { for(int i = head[x]; i; i = e[i].nxt) { int y = e[i].to; if(y == fat || e[i].lock) continue; dp(y, x); if(fir[y] + e[i].val > fir[x]) sec[x] = fir[x], fir[x] = fir[y] + e[i].val, maxp[x] = y; else if(fir[y] + e[i].val > sec[x]) sec[x] = fir[y] + e[i].val; } chkMax(res, fir[x] + sec[x]); } void dp2(int x, int fat, int f) { chkMin(res, max(f, fir[x])); for(int i = head[x]; i; i = e[i].nxt) { int y = e[i].to; if(y == fat || e[i].lock) continue; if(y == maxp[x]) dp2(y, x, max(sec[x] + e[i].val, f + e[i].val)); else dp2(y, x, max(fir[x] + e[i].val, f + e[i].val)); } } inline int getD(int rt) { res = 0; dp(rt, 0); return res; } inline int getR(int rt) { res = inf; dp2(rt, 0, 0); return res; } int main() { // freopen("4.in", "r", stdin); read(n); for(int x, y, v, i = 1; i < n; i++) { read(x), read(y), read(v); add(x, y, v), add(y, x, v); } dfs(1, 0, 0, 0); int root = 0; dis[root] = 0; for(int i = 1; i <= n; i++) if(dis[i] > dis[root]) root = i; dfs(root, 0, 0, 0); int pnt = 0; for(int i = 1; i <= n; i++) if(dis[i] > dis[pnt]) pnt = i; for(int i = pnt; i != root; i = e[toEdge[i] ^ 1].to) dia[++len] = toEdge[i]; int ans = dis[pnt]; for(int i = 1; i <= len; i++) { int now = 0; e[dia[i]].lock = e[dia[i] ^ 1].lock = 1; memset(fir, 0, sizeof(fir)); memset(sec, 0, sizeof(sec)); memset(maxp, 0, sizeof(maxp)); chkMax(now, getD(e[dia[i]].to)), chkMax(now, getD(e[dia[i] ^ 1].to)); chkMax(now, getR(e[dia[i]].to) + getR(e[dia[i] ^ 1].to) + e[dia[i]].val); e[dia[i]].lock = e[dia[i] ^ 1].lock = 0; chkMin(ans, now); } printf("%d\n", ans); return 0; }