SP8097 IOIISL08 - Islands

\(\texttt{Description}\)

SPOJ IOIISL08

vjudge SPOJ IOIISL08

洛谷 SP8097

  • 给出有 \(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}\)

Submission

$\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);
}
posted @ 2023-04-10 21:33  lzyqwq  阅读(17)  评论(0)    收藏  举报