BZOJ1023: [SHOI2008]cactus仙人掌图(单调队列优化DP)

传送门

题意:
求一颗仙人掌的直径。

题解:
DP。

首先建出图的DFS树。

因为是仙人掌图,所以每个环必定有一个dfs序最小的点,连接着若干条后向边和树边,表示环上的边或者割边。

记录 f[i] 表示dfs树上以 i 为根的子树(子图)中最长链。考虑DP:

1.对于一条割边, 对答案的影响为f[i]+f[v]+1,之后直接 f[i]=max{f[i],f[v]+1}
2.对于环边先不处理,等到dfs完所有子树后重新枚举边查找环上的儿子。(因为是在dfs树上所以连接的儿子一定是环上最后一个点。)
在多个环上分别dp。

对于环的dp:
首先,只需更新dfs树中最高点的 f[i] 。因为dfs树上对前面的点有影响的只有最高点,只需用最高点储存环上信息。直接枚举所有点, f[i]=max(f[i],maxjcir(i){f[j]+dis(i,j)})

其次,环上的两个不是最高点的点可能对答案产生影响,就像一个环上挂了许多子树,每个子树的最长链为 f[j] ,求 maxj1,j2cir(i){f[j1]+f[j2]+dis(i,j)} 。这是一个经典的单调队列优化dp。但是,与求基环外向树直径不同的是,这里的 dis(i,j) 取环上的最小值,所以只能从 i(n/2)+1 开始枚举。

#include<bits/stdc++.h>
using namespace std;
streambuf *ib,*ob;
inline void init(){
    ios::sync_with_stdio(false);
    cin.tie(NULL);cout.tie(NULL);
    ib=cin.rdbuf();ob=cout.rdbuf();
}
inline int read(){
    char ch=ib->sbumpc();int i=0,f=1;
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=ib->sbumpc();}
    while(isdigit(ch)){i=(i<<1)+(i<<3)+ch-'0';ch=ib->sbumpc();}
    return i*f;
}
int buf[50];
inline void W(int x){
    if(!x){ob->sputc('0');return;}
    if(x<0){ob->sputc('-');x=-x;}
    while(x){buf[++buf[0]]=x%10;x/=10;}
    while(buf[0]){ob->sputc(buf[buf[0]--]+'0');}
}
const int Maxn=5e4+50;
int n,m,f[Maxn],dfn[Maxn],ind,low[Maxn],fa[Maxn],dep[Maxn],ans;
vector<int>edge[Maxn];
inline void dp(int rt,int x){
    static int a[Maxn*2],que[Maxn*2],head,tail,cnt;
    cnt=dep[x]-dep[rt]+1;
    for(int i=cnt,y=x;i>=1;i--,y=fa[y])a[i]=y;
    int lim=cnt/2;
    memcpy(a+cnt+1,a+1,sizeof(int)*lim);
    que[head=tail=1]=1;
    int t=cnt;cnt+=lim;
    for(int i=2;i<=cnt;i++){
        while(head<=tail&&que[head]<i-lim)head++;
        ans=max(ans,f[a[i]]+i+f[a[que[head]]]-que[head]);
        while(head<=tail&&f[a[que[tail]]]-que[tail]<=f[a[i]]-i)tail--;
        que[++tail]=i;
    }
    for(int i=2;i<=t;i++)
        f[rt]=max(f[rt],f[a[i]]+min(i-1,t-i+1));
}
inline void dfs(int now,int father){
    dep[now]=dep[father]+1;low[now]=dfn[now]=++ind;fa[now]=father;
    for(int e=edge[now].size()-1;e>=0;e--)
    {
        int v=edge[now][e];
        if(v==father)continue;
        if(!dfn[v]){
            dfs(v,now);
            low[now]=min(low[now],low[v]);
        }
        else low[now]=min(low[now],dfn[v]);
        if(dfn[now]<low[v]){
            ans=max(ans,f[now]+f[v]+1);
            f[now]=max(f[now],f[v]+1);
        }
    }
    for(int e=edge[now].size()-1;e>=0;e--)
    {
        int v=edge[now][e];
        if(v==father)continue;
        if(fa[v]!=now&&dfn[v]>dfn[now])
        dp(now,v);
    }
}
int main()
{
    init();n=read(),m=read();
    for(int i=1;i<=m;i++)
    {
        int k=read(),a=read();
        for(int i=2;i<=k;i++)
        {
            int b=read();
            edge[a].push_back(b);
            edge[b].push_back(a);
            a=b;
        }
    }
    dfs(1,0);
    W(ans);ob->sputc('\n');
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值