P4381 [IOI2008]Island
聯賽前做點樹論
題意:給一個基環樹森林,求每個聯通塊的直徑和,\(n\le 10^6\),別看人話這麼短,原題面看了我5分鐘
對於一顆基環樹,我們可以提取環上的點。提取完可以看看有什麼性質。
這題,如果把環上的點拎出來,發現直徑可以被劃分為兩部分計算
- 經過至多環上一個點的路徑
- 經過至少兩個環上點的路徑
對於每個聯通快取較大者相加即為答案
這麼分的原因是:第一部分很好算。
在提取出環上的點之後,只需要跑一遍以它為根的子樹的直徑即可。注意這裡的子樹指“只經過至多一個環上點(即它自己)的聯通塊”。至於如何限制這個聯通塊,只需要把這個環上的點當作根,判斷一下出邊是否在環上即可,不在環上才過去。
由於求了直徑,最好選取一種可以順帶求出“子樹”內最長鏈的方法,不如後面還得單獨求一遍。
設“子樹”內最長鏈為 \(f_i\)
第二部分有個性質,就是這個路徑長度等於 \(f_i+f_j+dis_{i,j}\) ,其中 \(i,j\) 是環上兩點, \(dis_{i,j}\) 是它們在環上的距離
這個距離是個二維陣列,處理出來是 \(O(n^2)\) 的,肯定要化簡
考慮對於環按某一個方向記字首和 \(sum_i\) ,順時針還是逆時針隨便
那麼路徑長等於 \(f_i+f_j+\max(sum_j-sum_i,sum_n-(sum_j-sum_i))(j>i)\)
帶了個 \(\max\)
後來想到破環成鏈就簡單了
把陣列倍長,那麼直接統計 \(f_i+f_j+sum_j-sum_i(j-n<i<j)\) 的最大值就行了
發現這個東西顯然可以單調佇列搞,每個元素的權值設為 \(f_i-sum_i\) ,佇列維護合法的最大的隊首
注意這裡的“合法”僅要求 \(j-n<i<j\) ,所以對於佇列中的元素記個下標即可
列舉 \(i\) ,每次取出隊首更新最大值就做完了。
然而一調就是 \(40\) 分鐘。
細節:
- 最終答案是“每個聯通塊‘第一類與第二類最大值’的和”!不要分別取最大值輸出!(而且因為資料水,單獨輸出第二類最大值就有85分,很容易被迷惑)
- 單調佇列一定要判空!不空的時候再更新答案。因為元素有負數。
STL雖然慢,但是還是有好處的,大部分時間我都在調這個了/ll
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef double db;
#define x first
#define y second
#define sz(v) (int)v.size()
#define pb(x) push_back(x)
#define mkp(x,y) make_pair(x,y)
inline int read(){
int x=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=0;c=getchar();}
while(isdigit(c))x=x*10+c-'0',c=getchar();
return f?x:-x;
}
#define N 1000005
int n, H, T, id[N << 1], valp[N];
LL val[N << 1], sum[N << 1], Mxd, Mxlen, ans;
LL f[N], g[N], fa[N], dfn[N], tmr, loop[N], cnt, len[N];
bool onlp[N];
struct edge{
int nxt, to, val;
}e[N << 1];
int head[N], num_edge;
void addedge(int fr, int to, int val) {
++ num_edge;
e[num_edge].val = val;
e[num_edge].to = to;
e[num_edge].nxt = head[fr];
head[fr] = num_edge;
}
void dfs(int u, int ft){
dfn[u] = ++ tmr;
for (int i = head[u]; i; i = e[i].nxt) {
int v = e[i].to; if (v == ft) continue;
if (dfn[v]) {
if (dfn[v] < dfn[u]) continue;
loop[++ cnt] = v, onlp[v] = 1, valp[cnt] = e[i].val;
while (v != u) loop[++ cnt] = fa[v], onlp[fa[v]] = 1, valp[cnt] = len[v], v = fa[v];
}
else fa[v] = u, len[v] = e[i].val, dfs(v, u);
}
}
void getd(int u, int ft) {
for (int i = head[u]; i; i = e[i].nxt) {
int v = e[i].to; if (v == ft || onlp[v]) continue;
getd(v, u);
if (f[v] + e[i].val > f[u]) g[u] = f[u], f[u] = f[v] + e[i].val;
else if (f[v] + e[i].val > g[u]) g[u] = f[v] + e[i].val;
}
Mxd = max(Mxd, f[u] + g[u]);
}
void solve(int rt){
Mxd = Mxlen = 0;
cnt = 0, dfs(rt, 0);
for (int i = 1; i <= cnt; ++ i) getd(loop[i], 0), loop[i + cnt] = loop[i], valp[i + cnt] = valp[i];
for (int i = 1; i <= cnt << 1; ++ i) sum[i] = sum[i - 1] + valp[i];
H = 1, T = 0;
for (int i = 1; i <= cnt << 1; ++ i) {
while (H <= T && id[H] <= i - cnt) ++ H;
if (H <= T) Mxlen = max(Mxlen, val[H] + f[loop[i]] + sum[i]);
LL w = f[loop[i]] - sum[i];
while(H <= T && val[T] < w) -- T;
++T, val[T] = w, id[T] = i;
}
ans += max(Mxlen, Mxd);
}
signed main() {
n = read();
for (int i = 1; i <= n; ++ i) {
int x = i, y = read(), z = read();
addedge(x, y, z), addedge(y, x, z);
}
for (int i = 1; i <= n; ++ i) if (!dfn[i]) solve(i);
printf("%lld\n", ans);
return 0;
}