题解:P4244 [SHOI2008] 仙人掌图 II

题意

本文同步自洛谷专栏,题目传送门

求一棵仙人掌的直径。

分析

首先利用 tarjan 求点双,构建出一棵圆方树,要求圆点、方点相间。建方点时,将当前 dfs 的 \(u\) 作为该方点的父亲,其余的点作为儿子。

在这棵圆方树上 dp 求解。

如果当前点为圆点 \(u\),像一般的树上 dp 求直径一样,记录 \(fi,se\) 表示最大值和次大值,更新一下答案,并记录下 \(mx_u=fi\) 以供方点使用。

如果为方点 \(u\),记 \(half\) 为环长一半(下取整),先求一遍 \(u\) 子树内的点到 \(u\) 的父亲的距离的最大值,以返回给圆点。

然后再考虑该点双内的情况,此时情况类似基环树,首先倍长环,断环为链。

然后每一个点有点权 \(mx_i\),求 \(\max_{j<i\land i-j\le half}(mx_i+mx_j+i-j)\)

前一个限制在顺序扫描过程中容易实现,后一个限制形如滑动窗口,使用单调队列维护满足条件的最大的 \(mx_i-i\) 即可。

时间复杂度线性。

实现

实现上,个人采用的是圆点方点分别写一个 dfs 函数,可能更清晰一些。

$\red{\text{code}}$
#include<bits/stdc++.h>
using namespace std;
#define gc() (rp1==rp2&&(rp2=(rp1=buf)+fread(buf,1,IO,stdin)),*rp1++)
#define N 100005

const int IO=1<<22;
char buf[IO+1],*rp1,*rp2;
int n,m,scc,fa[N],stk[N<<1];//fa 方点的圆点父亲
int dfn[N],low[N],tot,tp;
int siz[N],mx[N],res,Q[N];//单调队列
vector<int>E[N],G[N];//G:方点信息

inline int read(){
    int a=0,c=gc();
    while(!isdigit(c)) c=gc();
    while(isdigit(c)) a=10*a+c-'0',c=gc();
    return a;
}

void tarjan(int u,int pre){
    dfn[u]=low[u]=++tot,stk[++tp]=u;
    for(auto &v:E[u]){
        if(!dfn[v]){
            tarjan(v,u),low[u]=min(low[v],low[u]);
            if(low[v]<dfn[u]) continue;
            scc++,fa[scc]=u;//父亲可以在链头或链尾
            do{
                G[scc].emplace_back(stk[tp--]);
            }while(stk[tp+1]!=v);//特别注意此处
            //不要误写为 while(stk[tp]!=u) ……
            siz[scc]=G[scc].size();//环长-1
        }
        else if(v!=pre) low[u]=min(low[u],dfn[v]);
    }
}

inline void cmax(int &a,int b){(a<b)&&(a=b);}
void dfs1(int u);
int dfs2(int u);

void dfs1(int u){//对圆点 dp
    int fi=0,se=0,tmp;
    for(auto &v:E[u]){
        tmp=dfs2(v);//先遍历所有方点
        if(tmp>fi) swap(fi,tmp);
        if(tmp>se) swap(se,tmp);
    } 
    cmax(res,se+fi),mx[u]=fi;
}

int dfs2(int u){//对方点 dp
    for(auto &v:G[u]) dfs1(v);
    tp=0;//建议在这里清空,比较清晰,以防出错
    for(auto &v:G[u]) stk[++tp]=mx[v];//复用了 stk 数组
    int half=(siz[u]+1)>>1,ret=0;//环长一半下取整
    for(int i=1;i<=half;i++) cmax(ret,i+stk[i]);//算出到根距离
    for(int i=half+1;i<=siz[u];i++) cmax(ret,siz[u]-i+1+stk[i]);
    stk[++tp]=0,Q[1]=1;//倍长环
    for(auto &v:G[u]) stk[++tp]=mx[v];
    for(int i=2,h=1,t=2;i<=tp;i++){//单调队列,限长 half,[h,t)
        while(h!=t&&Q[h]<i-half) h++;
        cmax(res,stk[i]+stk[Q[h]]+i-Q[h]);
        while(h!=t&&stk[Q[t-1]]-Q[t-1]<=stk[i]-i) t--;
        Q[t++]=i; 
    }
    return ret;
}

int main(){
    n=read(),m=read();
    for(int i=1,k,x,y;i<=m;i++){
        k=read(),x=read();
        for(int j=2;j<=k;j++){
            y=read(),E[x].emplace_back(y);
            E[y].emplace_back(x),x=y;
        }
    }
    tarjan(1,0);//构建圆方树
    for(int i=1;i<=n;i++) E[i].clear();
    for(int i=1;i<=scc;i++) E[fa[i]].emplace_back(i);
    tp=0,dfs1(1),printf("%d",res);
    return 0;
}

posted @ 2026-06-05 11:05  Wxb2010  阅读(11)  评论(0)    收藏  举报