Problem
题意:
求点数为 \(n\) 的基环树森林的直径和。
Solution
对于一棵树的直径是很简单的,可以用 DP 或者搜索完成。
对于基环树,我们可以借鉴树的 DP 思路。
树上的 DP 思路:
-
定义 \(D_i\) 表示从点 \(i\) 到以 \(i\) 为根的子树能到的最远长度,\(S_i\) 表示以 \(i\) 为根的子树的直径。
-
从叶子节点向上 DP:
而基环树的 DP 思路也大同小异,我们先用上面的方法将以环上的点为根的子树的 \(D\) 数组跑出来。
- 设 \(T\) 为基环树环的点集。
接下来就将问题转化为从 \(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\) ,所以又将式子转化为
又因为在链上,所以当前点 \(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;
}