[题解]P4381 [IOI2008]Island
myblog
题意
给你一个基环树森林,要你求森林的直径之和(一棵基环树的直径要求点和边都不重复)
Sol
把环看成根,考虑两种情况,直径不经过根和经过根
不经过环的
不经过根的很好做,对于根的每棵子树求直径就可以了
经过环的
设
对环上的边权前缀和一下为
整理一下
设环上边权总和为
所以只要求一下
所以第二种情况的答案为
code
代码超级简洁
#include <queue>
#include <cstdio>
#include <climits>
#include <algorithm>
typedef long long LL;
const int N = 1e6 + 30;
int n;
int to[N], w[N], in[N];
LL ans;
LL f[N], g[N];
std::queue < int > q;
int read()
{
int ss = 0, ff = 1; char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') ff = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') ss = ss * 10 + ch - '0', ch = getchar();
return ss * ff;
}
LL get(int p)
{
int tmp = p; p = to[p];
LL m1 = f[tmp], m2 = f[tmp], s = w[tmp], ans1 = g[tmp], ans2 = LLONG_MIN;
while (p != tmp)
{
in[p] = 0;
ans1 = std::max(ans1, std::max(g[p], f[p] + s + m1));
ans2 = std::max(ans2, f[p] - s + m2);
m1 = std::max(m1, f[p] - s), m2 = std::max(m2, f[p] + s);
s += w[p], p = to[p];
}
return std::max(ans1, ans2 + s);
}
int main()
{
n = read();
for (int i = 1; i <= n; ++i) to[i] = read(), w[i] = read(), ++ in[to[i]];
for (int i = 1; i <= n; ++i)
if (!in[i]) q.push(i);
while (!q.empty())
{
int p = q.front(); q.pop();
LL c = f[p] + w[p];
g[to[p]] = std::max(g[to[p]], std::max(f[to[p]] + c, g[p]));
f[to[p]] = std::max(f[to[p]], c);
if (!--in[to[p]]) q.push(to[p]);
}
for (int i = 1; i <= n; ++i)
if (in[i]) ans += get(i);
printf ("%lld\n", ans);
return 0;
}