1. 程式人生 > >Luogu 3761 [TJOI2017]城市

Luogu 3761 [TJOI2017]城市

string \n inf dia class tdi 兩個 amp memset

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; }
View Code

Luogu 3761 [TJOI2017]城市