「题解」IOI2008 Island

Problem

Link

题意:

求点数为 \(n\) 的基环树森林的直径和。

Solution

对于一棵树的直径是很简单的,可以用 DP 或者搜索完成。

对于基环树,我们可以借鉴树的 DP 思路。

树上的 DP 思路:

  • 定义 \(D_i\) 表示从点 \(i\) 到以 \(i\) 为根的子树能到的最远长度,\(S_i\) 表示以 \(i\) 为根的子树的直径。

  • 从叶子节点向上 DP:

\[S_i= \max_{u,v \in son(i) , u \neq v} D_u + D_v \\ D_i= \max_{u \in son(i)} D_u + dis_{i,u} \]

而基环树的 DP 思路也大同小异,我们先用上面的方法将以环上的点为根的子树的 \(D\) 数组跑出来。

  • 设 \(T\) 为基环树环的点集。

\[ans= \max_{u,v \in T , u \neq v} D_u + D_v + dis_{u,v} \]

接下来就将问题转化为从 \(T\) 中选两个不同的点 \(u,v\) ,使得 \(D_u + D_v + dis_{u,v}\) 最大。

这是个环,而对于环我们有一个很经典的技巧,就是破环成链。

再看到式子, \(D_u,D_v\) 都是独立的 \(u,v\),而 \(dis_{u,v}\) 与 \(u,v\) 都有关,所以我们要将它分成独立的关于 \(u,v\) 的结构。

不难想到在破成的链上求边权的前缀和,两两作差就能求得他们之间的 \(dis\) 。

设前缀和数组为 \(pre\) 。

所以式子变成了求最大的 \(D_u + D_v + pre_u - pre_v\) 。

再转化:

我们循环过程中每次可以确定 \(u\) ,所以又将式子转化为

\[D_u + pre_u + \max_{v \in T, u \neq v} D_v - pre_v \]

又因为在链上,所以当前点 \(u\) ,与最大值所在的 \(v\) 点,(设 \(id\) 为下标)\(id_u-id_v < size_T\)

这东西很像滑动窗口,所以如法炮制,我们可以用 \(\operatorname{O(n)}\) 的单调队列来维护点 \(u\) 之前 \(D_v - pre_v\) 的最大值。

均摊下来就是 \(\operatorname{O(1)}\) 的维护的复杂度了。

Code

写得很冗,常数也大,找环不一定用拓扑。。。

#include <queue>
#include <cstdio>
#include <cstring>

using namespace std;

typedef long long LL;
const int Maxn = 1e6;

int n, tot, len;
LL ans, pre[Maxn * 2 + 5], d[Maxn + 5];
bool worked[Maxn + 5], bl[Maxn * 2 + 5];
int nd[Maxn + 5], h[Maxn * 2 + 5], que[Maxn + 5], head[Maxn + 5], du[Maxn + 5], inq[Maxn + 5], val[Maxn + 5];

struct Edge {
	int ver, nxt, cst, id;
} edge[Maxn * 2 + 5];

namespace Function {
    template < typename _T >
    _T Abs (_T x) { return x < 0 ? -x : x; }
    template < typename _T >
    _T Max (_T x, _T y) { return x > y ? x : y; }
    template < typename _T >
    _T Min (_T x, _T y) { return x < y ? x : y; }
    template < typename _T >
    _T Gcd (_T x, _T y) { return !y ? x : Gcd (y, x % y); }
}

using namespace Function;

int Read () {
	int x = 0, f = 1; char c = getchar ();
	while (c < '0' || c > '9') {
		if (c == '-') f = -1; c = getchar ();
	}
	while ('0' <= c && c <= '9') {
		x = (x << 1) + (x << 3) + c - '0'; c = getchar ();
	}
	return x * f;
}

void Link (int u, int v, int w, int id) {
	edge[++ tot] = {v, head[u], w, id};
	head[u] = tot, du[u] ++;
}

void Search (int u) {
	worked[u] = 1;
	nd[++ len] = u;
	for (int i = head[u], v; i; i = edge[i].nxt) {
		v = edge[i].ver;
		if (worked[v]) continue;
		Search (v);
	}
}

void Topo () {
	queue < int > Q;
	for (int i = 1; i <= len; i ++) {
		if (du[nd[i]] == 1) {
			Q.push (nd[i]);
			inq[nd[i]] = 1;
		}
	}
	
	while (Q.size ()) {
		int u = Q.front ();
		Q.pop ();
		for (int i = head[u], v; i; i = edge[i].nxt) {
			v = edge[i].ver;
			if (inq[v]) continue;
			du[v] --;
			if (du[v] == 1) {
				Q.push (v);
				inq[v] = 1;
			}
		}
	}
}

void DP (int u) {
	inq[u] = 0;
	for (int i = head[u], v, w; i; i = edge[i].nxt) {
		v = edge[i].ver;
		w = edge[i].cst;
		if (!inq[v]) continue;
		DP (v);
		ans = Max (ans, d[u] + d[v] + w);
		d[u] = Max (d[u], d[v] + w);
	}
}

LL Work () {
	Topo ();
	int cnt = 0, node = 0;
	for (int i = 1; i <= len; i ++) {
		if (!inq[nd[i]]) {
			cnt ++;
			node = nd[i];
		}
	}
	
	h[1] = node;
	for (int i = 2; i <= cnt + 1; i ++) {
		for (int j = head[h[i - 1]], v, w; j; j = edge[j].nxt) {
			v = edge[j].ver;
			w = edge[j].cst;
			if (!inq[v] && !bl[edge[j].id]) {
				bl[edge[j].id] = 1;
				h[i] = v;
				val[i] = w;
				break;
			}
		}
	}
	
	for (int i = 2; i <= cnt; i ++) {
		h[i + cnt] = h[i];
		val[i + cnt] = val[i];
	}

	ans = 0;	
	for (int i = 1; i <= cnt; i ++) {
		DP (h[i]);
	}
	
	int hd = 1, tl = 0;
	for (int i = 1; i <= cnt * 2; i ++) {
		pre[i] = pre[i - 1] + val[i];
		while (hd <= tl && i - que[hd] + 1 > cnt) hd ++;
		ans = Max (ans, d[h[i]] + pre[i] + d[h[que[hd]]] - pre[que[hd]]);
		while (hd <= tl && d[h[que[tl]]] - pre[que[tl]] <= d[h[i]] - pre[i]) tl --;
		que[++ tl] = i;
	}
	
	memset (d, 0, sizeof d);
	memset (bl, 0, sizeof bl);
	memset (que, 0, sizeof que);
	memset (inq, 0, sizeof inq);
	
	return ans;
}

int main () {
	n = Read ();
	
	for (int i = 1, v, w; i <= n; i ++) {
		v = Read (), w = Read ();
		Link (i, v, w, i);
		Link (v, i, w, i);
	}
	
	LL ret = 0;
	for (int i = 1; i <= n; i ++) {
		if (!worked[i]) {
			len = 0;
			Search (i);
			ret += Work ();
		}
	}
	
	printf ("%lld", ret);
	return 0;
}
上一篇:jQuery图片Lightbox灯箱点击图片弹出放大图片


下一篇:[LeetCode] 827. Making A Large Island