题解 【P4381 [IOI2008] Island】
[IOI2008] Island
题意:
给定 \(n\)个点 \(n\)条边,每条边有边权。你可以从任意一个点开始,任意两点间不管有没有边都可以走,若两点间有边,则将边权加入答案,记录总和。其中,每个点只能经过一次。求答案的最大值。
分析:
显然,该图是一个基环树。对于环,我们有两种解决方法,即破环成链和倍长。
接下来,预处理出树的遍历顺序(即时间戳),顺便找到树上的环,并记录下来。首先破环求各个环上节点的子树直径,并记录 每个点在自己的子树中的最长距离(即代码中的 \(d\)数组),然后再将环倍长,通过单调队列优化记录答案。
code:
#include <bits/stdc++.h>
#define int long long
#define N 2000010
using namespace std;
int n, p, ans, cnt, num, answer, d[N], s[N], q[N], fa[N], dfn[N], sum[N], head[N];
bool v[N];
struct xcj{
int to, nxt, value;
} e[N];
void add(int u, int v, int w){e[++cnt] = {v, head[u], w}, head[u] = cnt;}//链式前向星存图
void get_cycle(int x, int y, int z){//找环
sum[1] = z;
while (y != x) {
s[++p] = y;
sum[p + 1] = e[fa[y]].value;
y = e[fa[y] ^ 1].to;
}
s[++p] = x;
for (int i = 1; i <= p; i++) {
v[s[i]] = true;
s[p + i] = s[i];
sum[p + i] = sum[i];
}
for (int i = 1; i <= 2 * p; i++) sum[i] += sum[i - 1];
}
void dfs1(int x){
dfn[x] = ++num;//记录时间戳
for (int i = head[x]; i; i = e[i].nxt){
int y = e[i].to;
if (!dfn[y]) fa[y] = i, dfs1(y);
else if ((i ^ 1) != fa[x] && dfn[y] > dfn[x]) get_cycle(x, y, e[i].value);//判环
}
}
void dfs(int x){//dp求树的直径
v[x] = 1;
for (int i = head[x]; i; i = e[i].nxt){
int y = e[i].to;
if (v[y]) continue;
dfs(y), ans = max(ans, d[x] + d[y] + e[i].value), d[x] = max(d[x], d[y] + e[i].value);//更新最大距离
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin >> n, cnt = 1;//链式前向星从2开始连边可以通过异或来寻找两点之前的无向边
for (int i = 1, v, w; i <= n; ++i) cin >> v >> w, add(i, v, w), add(v, i, w);
for (int u = 1; u <= n; ++u)//遍历每个节点
if (!dfn[u]){
p = ans = 0;
dfs1(u);
for (int i = 1; i <= p; i++) dfs(s[i]);//破环求子树直径
int l = 1, r = 0;
for (int i = 1; i <= 2 * p; i++){//单调队列优化
while (l <= r && q[l] <= i - p) l++;
if (l <= r) ans = max(ans, d[s[i]] + d[s[q[l]]] + sum[i] - sum[q[l]]);
while (l <= r && d[s[q[r]]] - sum[q[r]] <= d[s[i]] - sum[i]) r--;
q[++r] = i;
}
answer += ans;
}
cout << answer << endl;
return 0;
}
浙公网安备 33010602011771号