话说这是去年大爷的一道NOIP模拟赛题,对着大爷的代码看了一堂课的我终于把这题写掉了。
本题要求在基环树给定的环上删去一条边使剩下的树的直径最小,输出这个最小直径。
那么基环树可以画成这样子的:
有一些在环上的点,还有一些不在环上的点,显然只能在环上断边,我们预处理找出这个环$a_1 - a_m$,然后处理出环上的每一条边的权值$eVal_i$。
先考虑怎么找环,可以用一个栈记录一下系统栈中的元素,当再一次走到一个已经走过的结点的时候把栈里的元素一个一个弹出直到当前元素出栈为止,然后这些元素就是环上的点。
因为要在环上断边,不妨先断开$a_1$和$a_m$之间的边,然后我们预处理四个值$s0_i, s1_i, t0_i, t1_i$,$s0_i$表示从$a_1$开始在环上按照$1, 2, 3, ..., m$的顺序走走到边$a_i, a_{i + 1}$之前能走的最长路径,$s1_i$表示从$a_n$开始走$m, m - 1, m - 2, ..., 1$走到边$a_{i}, a_{i - 1}$之前能走到的最长路径,$t0_i$表示断开边$a_i, a_{i + 1}$$a_1$所在的联通块的直径,$t1_i$则表示断开边$a_{i}, a_{i - 1}$$a_n$所在的联通块的直径,这样子我们枚举一下环上的断边,用$max(t0_i, t1_{i + 1}, eVal_m + s0_i + s1_{i + 1})$更新答案即可。
考虑一下怎么计算$s$和$t$,我们依然可以预处理出从每一个环上的点($a_i = x$)开始不经过环上的点所能走到的最长链$fir_x$和直径$mx_i$,事实上,我们只要顺便计算一下次长链就可以算出直径,所以这些东西可以在一趟$dfs$里解决掉。
我们可以从小到大枚举$1-m$,然后记录一下环上走过的路径,然后顺便用沿路可能出现的最优解更新答案,其实$ans$的初值应该是$t0_m$所以在循环的时候应该注意在这趟循环中循环到$m$,要不然输出$0$……
$t$的计算方法同理。
然后就是码码码,感觉细节巨多。
时间复杂度$O(n)$。
Code:
#include <cstdio> #include <cstring> using namespace std; typedef long long ll; const int N = 2e5 + 5; int n, m = 0, tot = 0, head[N], a[N], top = 0, sta[N]; ll fir[N], sec[N], mx[N], s0[N], s1[N], t0[N], t1[N], eVal[N]; bool vis[N], in[N]; struct Edge { int to, nxt; ll val; } e[N << 1]; inline void add(int from, int to, ll val) { e[++tot].to = to; e[tot].val = val; e[tot].nxt = head[from]; head[from] = tot; } template <typename T> inline void read(T &X) { X = 0; char ch = 0; T 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; } template <typename T> inline T max(T x, T y) { return x > y ? x : y; } template <typename T> inline T min(T x, T y) { return x > y ? y : x; } template <typename T> inline void chkMax(T &x, T y) { if(y > x) x = y; } template <typename T> inline void chkMin(T &x, T y) { if(y < x) x = y; } bool getCir(int x, int fat) { if(vis[x]) { for(; ; ) { int now = sta[top]; in[now] = 1; a[++m] = now; --top; if(now == x) return 1; } } vis[x] = 1; sta[++top] = x; int tmp = top; for(int i = head[x]; i; i = e[i].nxt) { int y = e[i].to; if(y == fat) continue; if(getCir(y, x)) return 1; top = tmp; } return 0; } void getEval(int x, int to) { if(to == m + 1) return; for(int i = head[x]; i; i = e[i].nxt) { int y = e[i].to; if(y != a[to + 1]) continue; eVal[to] = e[i].val; getEval(y, to + 1); } } ll getMx(int x, int fat) { ll res = 0LL; for(int i = head[x]; i; i = e[i].nxt) { int y = e[i].to; if(y == fat || in[y]) continue; ll tmp = getMx(y, x), now = fir[y] + e[i].val; chkMax(res, tmp); if(now > fir[x]) sec[x] = fir[x], fir[x] = now; else if(now > sec[x]) sec[x] = now; } chkMax(res, fir[x] + sec[x]); return res; } int main() { // freopen("tube2.in", "r", stdin); read(n); for(int i = 1; i <= n; i++) { int x, y; ll v; read(x), read(y), read(v); add(x, y, v), add(y, x, v); } getCir(1, 0); a[m + 1] = a[1]; /* for(int i = 1; i <= m; i++) printf("%d ", a[i]); printf(" "); */ getEval(a[1], 1); /* for(int i = 1; i <= m; i++) printf("%lld ", eVal[i]); printf(" "); */ for(int i = 1; i <= m; i++) mx[i] = getMx(a[i], 0); /* for(int i = 1; i <= m; i++) printf("%lld ", mx[i]); printf(" "); */ ll tmp = 0LL; for(int i = 1; i < m; i++) { s0[i] = max(s0[i - 1], tmp + fir[a[i]]); tmp += eVal[i]; } tmp = 0LL; for(int i = m; i > 1; i--) { s1[i] = max(s1[i + 1], tmp + fir[a[i]]); tmp += eVal[i - 1]; } tmp = 0LL; for(int i = 1; i <= m; i++) { t0[i] = max(t0[i - 1], max(mx[i], tmp + fir[a[i]])); chkMax(tmp, fir[a[i]]); tmp += eVal[i]; } tmp = 0LL; for(int i = m; i > 1; i--) { t1[i] = max(t1[i + 1], max(mx[i], tmp + fir[a[i]])); chkMax(tmp, fir[a[i]]); tmp += eVal[i - 1]; } /* printf(" "); for(int i = 1; i <= m; i++) printf("%lld ", s0[i]); printf(" "); for(int i = 1; i <= m; i++) printf("%lld ", s1[i]); printf(" "); for(int i = 1; i <= m; i++) printf("%lld ", t0[i]); printf(" "); for(int i = 1; i <= m; i++) printf("%lld ", t1[i]); printf(" "); */ ll ans = t0[m]; for(int i = 1; i < m; i++) chkMin(ans, max(max(t0[i], t1[i + 1]), s0[i] + s1[i + 1] + eVal[m])); printf("%lld ", ans); return 0; }