题目链接
题目大意
给定一个基环树森林,求它们的直径之和。
题解
基环外向树的直径分两类:
1.在某一棵外向树内部(某个外向树的直径)。
2.一个端点在一棵外向树内,横跨环上的一段区间,另一个端点在另一颗外向树内。
首先,我们找到基环,然后对于每一个外向树都求出能到子树中可以延伸的最长的距离
pathi
p
a
t
h
i
,顺便求出外向树的直径,更新答案。
接着只需要考虑第二类情况了。任取一个节点
point
p
o
i
n
t
,记环上第
i
i
个节点离的距离为
disti
d
i
s
t
i
,基环上边的总距离为
sum
s
u
m
,那么答案为:
变形后可以得到:
注意: (disti+pathi) ( d i s t i + p a t h i ) 是可以事先算出来的。
于是通过一些简单的技巧我们就可以通过此题了。
代码
从这份代码中,大家可以看到我做题时经历的坎坷:
/*
这边使用了错误的动态规划,得分36pts
#include <cstdio>
#include <iostream>
typedef long long ll;
using namespace std;
const int maxn = 1000005;
const int maxm = 2000005;
bool vis[maxn], mrk[maxn];
int n, road[maxn][2];
int edge, ter[maxn], len[maxn], nxt[maxn], lnk[maxn];
int circle, head, tail, id[maxm], par[maxn], node[maxm];
ll ans, res, sum[maxm], path[maxn], val[maxm];
void addedge(int u, int v, int w) {
ter[++edge] = v;
len[edge] = w;
nxt[edge] = lnk[u];
lnk[u] = edge;
}
void tree_diameter(int u) {
mrk[u] = 1;
for (int i = lnk[u]; i; i = nxt[i]) {
int v = ter[i], w = len[i];
if (mrk[v]) {
continue;
}
tree_diameter(v);
res = max(res, path[u] + path[v] + w);
path[u] = max(path[u], path[v] + w);
}
}
void find_circle(int u) {
circle = 0, vis[u] = 1;
for (int v; ; u = v) {
v = road[u][0];
if (vis[v]) {
mrk[v] = 1, node[++circle] = v, sum[1] = road[v][1];
for (int i = u; i != v; i = par[i]) {
mrk[i] = 1, node[++circle] = i, sum[circle] = road[i][1];
}
for (int i = 1; i <= circle; i++) {
node[i + circle] = node[i];
sum[i + circle] = sum[i];
}
for (int i = 1; i <= circle << 1; i++) {
sum[i] += sum[i - 1];
}
break;
}
vis[v] = 1;
par[v] = u;
}
}
ll solve(int u) {
res = 0;
find_circle(u);
for (int i = 1; i <= circle; i++) {
tree_diameter(node[i]);
}
head = tail = 0;
for (int i = 1; i <= circle << 1; i++) {
pop(i - circle);
res = max(res, sum[i] + path[node[i]] - query());
push(i, sum[i] - path[node[i]]);
}
return res;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d %d", &road[i][0], &road[i][1]);
addedge(road[i][0], i, road[i][1]);
}
for (int i = 1; i <= n; i++) {
if (!mrk[i]) {
ans += solve(i);
}
}
printf("%lld\n", ans);
return 0;
}
*/
/*
这边使用了暴力,得分72pts
#include <cstdio>
#include <iostream>
typedef long long ll;
using namespace std;
const int maxn = 1000005;
const int maxm = 2000005;
bool vis[maxn], mrk[maxn];
int n, road[maxn][2];
int edge, ter[maxn], len[maxn], nxt[maxn], lnk[maxn];
int circle, head, tail, id[maxm], par[maxn], node[maxm];
ll ans, res, sum[maxm], path[maxn], val[maxm];
void push(int nid, int nval) {
while (head < tail && val[tail - 1] >= nval) {
tail--;
}
id[tail] = nid;
val[tail++] = nval;
}
void pop(int pid) {
while (head < tail && id[head] <= pid) {
head++;
}
}
ll query() {
return val[head];
}
void addedge(int u, int v, int w) {
ter[++edge] = v;
len[edge] = w;
nxt[edge] = lnk[u];
lnk[u] = edge;
}
void tree_diameter(int u) {
mrk[u] = 1;
for (int i = lnk[u]; i; i = nxt[i]) {
int v = ter[i], w = len[i];
if (mrk[v]) {
continue;
}
tree_diameter(v);
res = max(res, path[u] + path[v] + w);
path[u] = max(path[u], path[v] + w);
}
}
void find_circle(int u) {
circle = 0, vis[u] = 1;
for (int v; ; u = v) {
v = road[u][0];
if (vis[v]) {
mrk[v] = 1, node[++circle] = v, sum[1] = road[v][1];
for (int i = u; i != v; i = par[i]) {
mrk[i] = 1, node[++circle] = i, sum[circle] = road[i][1];
}
for (int i = 1; i <= circle; i++) {
node[i + circle] = node[i];
sum[i + circle] = sum[i];
}
for (int i = 1; i <= circle << 1; i++) {
sum[i] += sum[i - 1];
}
break;
}
vis[v] = 1;
par[v] = u;
}
}
ll solve(int u) {
res = 0;
find_circle(u);
for (int i = 1; i <= circle; i++) {
tree_diameter(node[i]);
}
for (int i = 1; i <= circle; i++) {
for (int j = 1; j < i; j++) {
res = max(res, max((sum[i] + path[node[i]]) - (sum[j] - path[node[j]]), sum[circle] - (sum[i] - path[node[i]]) + (sum[j] + path[node[j]])));
}
}
return res;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d %d", &road[i][0], &road[i][1]);
addedge(road[i][0], i, road[i][1]);
}
for (int i = 1; i <= n; i++) {
if (!mrk[i]) {
ans += solve(i);
}
}
printf("%lld\n", ans);
return 0;
}
*/
//下面就是正解了,得分100pts
#include <cstdio>
#include <iostream>
typedef long long ll;
using namespace std;
const int maxn = 1000005;
bool vis[maxn], mrk[maxn];
int n, road[maxn][2];
int edge, ter[maxn], len[maxn], nxt[maxn], lnk[maxn];
int circle, par[maxn], node[maxn];
ll mn, mx, ans, res, sum[maxn], path[maxn];
inline int read() {
int ret = 0;
char ch = getchar();
while (!isdigit(ch)) {
ch = getchar();
}
while (isdigit(ch)) {
ret = (ret << 1) + (ret << 3) + ch - '0';
ch = getchar();
}
return ret;
}
inline void addedge(int u, int v, int w) {
ter[++edge] = v;
len[edge] = w;
nxt[edge] = lnk[u];
lnk[u] = edge;
}
void find_circle(int u) {
circle = 0, vis[u] = 1;
for (int v; ; u = v) {
v = road[u][0];
if (vis[v]) {
mrk[v] = 1, node[++circle] = v, sum[1] = road[v][1];
for (int i = u; i != v; i = par[i]) {
mrk[i] = 1, node[++circle] = i, sum[circle] = road[i][1];
}
for (int i = 1; i <= circle; i++) {
sum[i] += sum[i - 1];
}
break;
}
vis[v] = 1;
par[v] = u;
}
}
void tree_diameter(int u) {
mrk[u] = 1;
for (int i = lnk[u]; i; i = nxt[i]) {
int v = ter[i], w = len[i];
if (mrk[v]) {
continue;
}
tree_diameter(v);
res = max(res, path[u] + path[v] + w);
path[u] = max(path[u], path[v] + w);
}
}
ll solve(int u) {
mn = 1e15, mx = -1e15, res = 0;
find_circle(u);
for (int i = 1; i <= circle; i++) {
tree_diameter(node[i]);
}
for (int i = 1; i <= circle; i++) {
res = max(res, max((sum[i] + path[node[i]]) - mn, sum[circle] - (sum[i] - path[node[i]]) + mx));
// printf("%lld %d\n", res, i);
mn = min(mn, sum[i] - path[node[i]]);
mx = max(mx, sum[i] + path[node[i]]);
}
return res;
}
int main() {
n = read();
for (int i = 1; i <= n; i++) {
road[i][0] = read(), road[i][1] = read();
addedge(road[i][0], i, road[i][1]);
}
for (int i = 1; i <= n; i++) {
if (!mrk[i]) {
ans += solve(i);
}
}
printf("%lld\n", ans);
return 0;
}