1. 程式人生 > 實用技巧 >P4381 [IOI2008]Island

P4381 [IOI2008]Island

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