SP8097 IOIISL08 - Islands
\(\texttt{Description}\)
给出有 \(n\) 个点的基环树森林,求每一棵基环树的直径之和。
\(n\le 10^5\)。
\(\texttt{Solution}\)
首先把环找出来,可以使用 tarjan 找边双连通分量。
然后对于森林中的每一棵基环树,其直径可能存在于以环 \(C\) 上节点为根的子树内,也可能经过环 \(C\),于是分两类讨论。
-
直径存在于以环上节点为根的子树内:
对于环上的每一个节点 \(u\),求出以其为根的子树的直径 \(f_u\)。用树形
dp解决。这一部分的贡献为 \(\max\limits_{u\in C} f_u\)。 -
直径经过环:
对于环上每一个节点 \(u\),求出以其为起点的最长链 \(g_u\)。同样用树形
dp解决。令环总长度为 \(\text{len}\)。还需要求出\(s_i\) 表示 \(C_1\rightarrow C_2 \rightarrow \dots\rightarrow C_i\) 的长度。然后 \(i,j\) 两点之间产生的直径就是 \(\max\{g_i+g_j+|s_i-s_j|,g_i+g_j+\text{len}-|s_i-s_j|\}\),因为可以朝两个方向走。这个形式不太好求,所以套路破坏环为链,将环复制一份,记为 \(C_{\text{len}+1}\sim C_{2\text{len}}\),这一部分的 \(s_i\) 定义为 \(C_1\rightarrow C_2 \rightarrow \dots\rightarrow C_1\rightarrow C_2\rightarrow C_{i-\text{len}}\) 的长度。这样可以考虑到两种方向的情况。所有贡献均在复制的环产生,且 \(C_i\) 的贡献为 \(\max\limits_{j=i-\text{len}+1}^{i-1}(g_i+g_j+s_i-s_j)\)。
这个式子可以改写成 \(g_i+s_i+\max\limits_{j=i-\text{len}+1}^{i-1}(g_j-s_j)\),注意到是定长区间,用单调队列维护 \(g_j-s_j\) 的最大值即可。
最后把所有直径加起来就行了。时间复杂度和空间复杂度均为 \(\mathcal{O}(n)\)。
\(\texttt{Code}\)
$\texttt{Click For Code}$
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;//不开 long long 见祖宗
const int N=1e5+5;
vector<int>ver[N];//环上的节点
int n,dfn[N],low[N],cnt=1,sum,ecc[N],hd[N],stk[N],top,tot,q[N<<1],h,t,id[N],num[N],sz[N];
ll ans,f[N],g[N],s[N<<1];
bool is[N],ic[N];//入栈,在环上
struct edge{
int v,ne;
ll w;
}e[N<<1];
void add(int u,int v,ll w){
e[++cnt]={v,hd[u],w};
hd[u]=cnt;
}
void tarjan(int u,int la){
dfn[u]=low[u]=++tot;
is[stk[++top]=u]=1;
for(int i=hd[u];i;i=e[i].ne){
if(i^(la^1)){//重边
if(!dfn[e[i].v]){
tarjan(e[i].v,i);
low[u]=min(low[e[i].v],low[u]);
}else if(is[e[i].v]){
low[u]=min(dfn[e[i].v],low[u]);
}
}
}
if(dfn[u]==low[u]){
++sum;
while(1){
int k=stk[top--];
is[k]=0;
ver[ecc[k]=sum].emplace_back(k);
++sz[sum];//大小
if(u==k){
break;
}
}
if(sz[sum]^1){//是环
for(int v:ver[sum]){
ic[v]=1;
}
}
}
}
void dp(int u,int fa){//树形 dp
ll max1=0,max2=0;
int p1;
for(int i=hd[u];i;i=e[i].ne){
if((e[i].v^fa)&&!ic[e[i].v]){
dp(e[i].v,u);
f[u]=max(f[u],f[e[i].v]);
g[u]=max(g[u],g[e[i].v]+e[i].w);
if(g[e[i].v]+e[i].w>max1){
max1=g[p1=e[i].v]+e[i].w;
}
}
}
for(int i=hd[u];i;i=e[i].ne){
if((e[i].v^fa)&&!ic[e[i].v]&&(e[i].v^p1)){
if(g[e[i].v]+e[i].w>max2){
max2=g[e[i].v]+e[i].w;
}
}
}
f[u]=max(f[u],max1+max2);
}
void dis(int u,int la,int st,int bel){//求出 s,同时需要把环上的节点和在环上的下标对应起来
for(int i=hd[u];i;i=e[i].ne){
if(ic[e[i].v]&&(i^(la^1))){
if(e[i].v^st){
s[id[e[i].v]=id[u]+1]=s[id[u]]+e[i].w;
num[id[e[i].v]]=e[i].v;
dis(e[i].v,i,st,bel);
}else{
s[sz[bel]+1]=s[id[u]]+e[i].w;
}
break;
}
}
}
ll solve(int bel){//单调队列
h=1;
t=0;
ll ret=0;
for(int i=2;i<=sz[bel];++i){//拆环
s[i+sz[bel]]=s[sz[bel]+1]+s[i];
num[i+sz[bel]]=num[i];
}
for(int i=1;i<=sz[bel];++i){
ret=max(ret,f[num[i]]);//子树内
while(h<=t&&g[num[i]]-s[i]>=g[num[q[t]]]-s[q[t]]){
--t;
}
q[++t]=i;
}
for(int i=sz[bel]+1;i<=(sz[bel]<<1);++i){//经过环
while(h<=t&&q[h]<=i-sz[bel]){
++h;
}
if(h<=t){
ret=max(ret,g[num[i]]+s[i]+g[num[q[h]]]-s[q[h]]);
}
while(h<=t&&g[num[i]]-s[i]>=g[num[q[t]]]-s[q[t]]){
--t;
}
q[++t]=i;
}
return ret;
}
int main(){
scanf("%d",&n);
for(int i=1,v;i<=n;++i){
ll w;
scanf("%d%lld",&v,&w);
add(i,v,w);
add(v,i,w);
}
for(int i=1;i<=n;++i){
if(!dfn[i]){
tarjan(i,0);
}
}
for(int i=1;i<=sum;++i){
if(ver[i].size()^1){
for(int v:ver[i]){//处理 f,g
dp(v,0);
}
fill(s+1,s+(sz[i]<<1)+1,0);//清空
fill(num+1,num+(sz[i]<<1)+1,0);
num[id[ver[i][0]]=1]=num[sz[i]+1]=ver[i][0];
dis(ver[i][0],0,ver[i][0],i);
ans+=solve(i);
}
}
printf("%lld",ans);
}

浙公网安备 33010602011771号