题目大意:给你一棵基环树,让你在环上删掉一条边,使得树的直径尽量小。
题解:不考虑删边的基环树的直径是如何做的:对环上每个点树形DP,
d
p
[
i
]
[
0
]
,
d
p
[
i
]
[
1
]
dp[i][0],dp[i][1]
dp[i][0],dp[i][1]分别记录i到以 i 为根的子树的最远距离和次远距离,用单调队列处理直径过环的情况。
环是双向的,处理过环情况时将环断开复制一倍,然后用单调队列枚举一个点做终点找能和它凑成直径的起点。
对于这题,由于一定要在环上删一条边,可以发现删完后环变成单向了,也就不需要复制一倍再处理。
具体做法:首先和不删边的过程一样对环上每个点做树形DP,因为直径可能不过环要考虑到这一点。在环上枚举被删的边,例如枚举边(i - 1,i) (i是环上的某一个点,用dfs将环找出来后存在一个一维数组中展成线性序列),那么考虑这时过环的最远距离分成三种情况:
可能在 (1,i - 1),两个点都在前半段
(i,len),两个点都在后半段
以及一个点在(1,i - 1),另一个点在(i,len),两个点跨段
对于前两种情况,由于删边之后是单向的,前两种状况直接在线性序列上用单调队列处理,做法和不删边的一样,但不用把环展成两倍,记录两个数组:prev[i]:1到 i 范围里构成的最长直径;sufv[i]:i 到 len范围内构成的最长直径。对于第三种情况,设
s
u
m
v
sum_v
sumv 为 环上边权的前缀和,发现答案变成了
s
u
m
u
−
s
u
m
v
+
s
u
m
l
e
n
+
d
p
[
u
]
[
0
]
+
d
p
[
v
]
[
0
]
sum_u - sum_v + sum_len + dp[u][0] + dp[v][0]
sumu−sumv+sumlen+dp[u][0]+dp[v][0],u在前半段,v在后半段(画个图就清楚了),所以再预处理两个数组分别记录前缀 最大的
s
u
m
u
+
d
p
[
u
]
[
0
]
sum_u + dp[u][0]
sumu+dp[u][0] 和后缀最大的
d
p
[
v
]
[
0
]
−
s
u
m
v
dp[v][0] - sum_v
dp[v][0]−sumv,因为找直径是每次找最大的两个值。
删掉一条边后的过环直径是上面三种情况的最大值,枚举删掉哪条边求出最小值,再和不过环的直径比较,取较大值。
整体复杂度O(n)
(糟糕的码风,不会压行)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e6 + 10;
int nxt[maxn],head[maxn],to[maxn],w[maxn],cnt;
int n,dfn[maxn],sz,vis[maxn],v[maxn],top;
ll val[maxn],dp[maxn][2];
int f[maxn],d[maxn];
void init() {
fill(head + 1,head + n + 1,-1);
sz = cnt = 0;
}
void add(int x,int y,int z) {
to[cnt] = y;
w[cnt] = z;
nxt[cnt] = head[x];
head[x] = cnt++;
}
void getloop(int u,int s,int id) {
dfn[u] = ++sz;
for(int i = head[u]; i + 1; i = nxt[i]) {
if(!dfn[to[i]]) {
f[to[i]] = u;
d[to[i]] = w[i];
getloop(to[i],u,i);
}
else if(dfn[to[i]] < dfn[u]) continue;
else if(dfn[to[i]] > dfn[u]) {
for(int p = to[i]; p != u; p = f[p]) {
v[++top] = f[p];
val[top] = d[p];
vis[f[p]] = 1;
}
vis[to[i]] = 1;
v[++top] = to[i];
val[top] = w[i];
}
}
}
void dfs(int u,int s,ll &ans) {
dp[u][0] = dp[u][1] = 0;
for(int i = head[u]; i + 1; i = nxt[i]) {
if(vis[to[i]] || to[i] == s) continue;
dfs(to[i],u,ans);
if(dp[to[i]][0] + w[i] > dp[u][0]) {
dp[u][1] = dp[u][0];
dp[u][0] = dp[to[i]][0] + w[i];
}
else if(dp[to[i]][0] + w[i] > dp[u][1])
dp[u][1] = dp[to[i]][0] + w[i];
}
ans = max(ans,dp[u][0] + dp[u][1]);
}
ll pre_g[maxn]; // 前缀sum + dp[i][0]最大
ll suf_h[maxn]; // 后缀 dp[i][0] - sum最大
ll pre_v[maxn]; // 前缀最长直径
ll suf_v[maxn]; // 后缀最长直径
ll q[maxn]; // 单调队列
int front,rear;
int main() {
scanf("%d",&n);
init();
for(int i = 1; i <= n; i++) {
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);add(y,x,z);
}
getloop(1,0,-1);
ll ans = 0;
for(int i = 1; i <= top; i++)
dfs(v[i],0,ans);
val[0] = 0;
for(int i = 1; i <= top; i++)
val[i] += val[i - 1];
int cur = 1;
front = rear = 0;
for(int i = 1; i <= top; i++) {
pre_g[i] = val[i] + dp[v[i]][0];
pre_v[i] = -1e15;
while(cur < i) {
while(rear > front && q[rear] < dp[v[cur]][0] - val[cur]) rear--;
q[++rear] = dp[v[cur]][0] - val[cur];
cur++;
}
if(front < rear)
pre_v[i] = dp[v[i]][0] + val[i] + q[front + 1];
}
pre_g[0] = pre_v[0] = -1e15;
for(int i = 1; i <= top; i++) {
pre_v[i] = max(pre_v[i],pre_v[i - 1]);
pre_g[i] = max(pre_g[i],pre_g[i - 1]);
}
front = rear = 0;
cur = top;
for(int i = top; i >= 1; i--) {
suf_h[i] = dp[v[i]][0] - val[i];
suf_v[i] = -1e15;
while(cur > i) {
while(rear > front && q[rear] < dp[v[cur]][0] + val[cur]) rear--;
q[++rear] = dp[v[cur]][0] + val[cur];
cur--;
}
if(front < rear)
suf_v[i] = dp[v[i]][0] - val[i] + q[front + 1];
}
suf_h[top + 1] = pre_v[top + 1] = -1e15;
for(int i = top; i >= 1; i--) {
suf_v[i] = max(suf_v[i],suf_v[i + 1]);
suf_h[i] = max(suf_h[i],suf_h[i + 1]);
}
ll res = 1e15;
for(int i = 1; i <= top; i++) {
ll tmp = -1e15;
tmp = max(tmp,suf_v[i]);
tmp = max(tmp,pre_v[i - 1]);
tmp = max(tmp,pre_g[i - 1] + suf_h[i] + val[top]);
res = min(res,tmp);
}
printf("%lld\n",max(ans,res));
return 0;
}