P4381 [IOI2008]Island

P4381 [IOI2008]Island

联赛前做点树论

题意:给一个基环树森林,求每个联通块的直径和,(nle 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
", ans);
	return 0;
}
路漫漫其修远兮,吾将上下而求索
原文地址:https://www.cnblogs.com/zzctommy/p/13880256.html