BZOJ-1023 [SHOI2008]仙人掌图

仙人掌基础题,结果一脸懵逼。。。
%PB大佬,瞬间讲懂这个dp(@ο@)

目录

题目

BZOJ-1023 [SHOI2008]cactus仙人掌图
大意就是求一个仙人掌图的直径,有点像TOJ3517,那个是求树的直径,而这个是仙人掌,有环。。。


思路

其实,如果没有环,那么直接dp[u]=max(dp[v]+1);
所以,我们考虑把环单独拿出来处理;

  • 联想tarjan求SCC的做法,我们dfs时如果当dfn[u]<dfn[v] && fa[v]!=u,那么这一定是在环里转了一圈,又回到环顶了;
  • 既然找到了环,我们把它摘出来单独处理就好了;
  • 在环中,dis(i,j)=min(j-i,n-i+j),所以我们直接用单调队列,队列里最多存n/2个点,而且队首最优;
  • 对于环上某个点u,它能参与构成的最长链=dp[u]+dp[q[head]]+dis(u,q[head]),其中dis(u,q[head])在这个队列中=u-q[head],所以dp[u]+u是已固定了,所以队首最优就是dp[head]-q[head];
  • 由于更新ans时,只转一圈可能会漏掉后1/4圈加前1/4圈倒着连成的最长链,所以要倍长这个环,转两圈;

代码

#include <cstdio>
#include <iostream>
using namespace std;
#define min(a,b) ((a)^(((a)^(b))&(-((a)>(b)))))
#define max(a,b) ((a)^(((a)^(b))&(-((a)<(b)))))

inline int read();

const int MAXN=50005;
int he[MAXN],cnt,n,m,index;
int dfn[MAXN],low[MAXN],de[MAXN];
int dp[MAXN]; //dp[i]表示以i为根的子树且i为一个端点的树边最长链
int tmp[MAXN<<1],fa[MAXN];
int q[MAXN<<1],head,tail;
int ans; //即直径

struct line{
    int to,nex;
}ed[MAXN<<2];

inline void add(const int &u,const int &v){
    ed[++cnt].nex=he[u];
    he[u]=cnt;
    ed[cnt].to=v;
}

inline void cir_dp(const int &rt,const int &x){
    int tot=de[x]-de[rt]+1;
    //由于dfs,这也就是环里点的个数
    for(int i=x;i!=rt;i=fa[i],--tot)
        tmp[tot]=dp[i];
    //tmp[i]是rt为最高点的环里的第i号点连向环外的最长链的长度
    tmp[tot]=dp[rt];
    tot=de[x]-de[rt]+1;
    //倍长这个环,
    //转两圈才能保证最开始的点的dp[]或tmp[]更新到最优
    for(int i=1;i<=tot;++i)
        tmp[i+tot]=tmp[i];
    //单调队列。。。
    q[1]=1; head=tail=1;
    for(int i=2;i<=(tot<<1);++i){
        //由于环中dis(i,j)=min(j-i,n-j+i),
        //所以干脆队列设为半个环长就好了
        while(head<=tail && i-q[head]>(tot>>1))
            ++head;
        //对于环中的每个点i,
        //它的实际最长链=以它为端点的树边最长链dp[i]或tmp[i]+它到环顶的距离
        ans=max(ans,tmp[i]+i+tmp[q[head]]-q[head]);
        //为保证队首最优,对于i,用它更新完ans后,
        //它对下一个点i'的贡献便是tmp[i]-i,顾有一下的出队条件
        while(head<=tail && tmp[q[tail]]-q[tail]<=tmp[i]-i)
            --tail;
        //i入队
        q[++tail]=i;
    }
    for(int i=2;i<=tot;++i) //把环里的最优解更新到环顶,于是环就无用了
        dp[rt]=max(dp[rt],tmp[i]+min(i-1,tot-i+1));
}

void dfs(const int &u){
    dfn[u]=low[u]=++index;
    for(int i=he[u],v;i;i=ed[i].nex){
        v=ed[i].to;
        if(v!=fa[u]){
            //没有访问过v
            if(!dfn[v]){
                fa[v]=u;
                de[v]=de[u]+1;
                dfs(v);
                low[u]=min(low[u],low[v]);
            }
            //类似tarjan,low[i]即为i所在连通块的min(dfn)
            else low[u]=min(low[u],dfn[v]);
            if(dfn[u]<low[v]){
                //u,v不在同一连通块,
                //即边(u,v)是树边,按树形dp跑即可
                ans=max(ans,dp[u]+dp[v]+1);
                dp[u]=max(dp[u],dp[v]+1);
            }
        }
    }
    for(int i=he[u],v;i;i=ed[i].nex){
        v=ed[i].to;
        //这种情况是u是环顶,dfs在环里转一圈到v了,
        if(fa[v]!=u && dfn[u]<dfn[v])
            cir_dp(u,v); //专门处理这个环
    }
}

int main(){
    n=read(),m=read();
    for(int i=1,k,u;i<=m;++i){
        k=read(),u=read();
        for(int j=1,v;j<k;++j){
            v=read();
            add(u,v),add(v,u);
            u=v;
        }
    }
    dfs(1);
    printf("%d\n",ans);
    return 0;
}

inline int read(){
    char c; int x;
    while(c=getchar(),c<'0' || '9'<c);
    x=c-'0';
    while(c=getchar(),'0'<=c && c<='9')
        x=x*10+c-'0';
    return x;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值