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