bzoj1023 [SHOI2008]cactus仙人掌图 树形DP+单调队列

题意:给一颗仙人掌求直径。
经典好题。
一开始naive的想以为缩点以后直接求,想了想感觉自己是傻子。。块内的根本无法统计。
大概能想到DP求解,但是单调队列真心被震惊到了= =
设f[x],表示以x为起点(从上往下)的最长路径,对于树边/非树边分别转移,树边当让直接转移了,主要是非树边,非树边就是环上边,我只用环上的点更新f[x](x为环上深度最小点),这个需要DP。
树边情况,对于 f[x],f[x]=max(f[v]+1)vson
非树边情况:
已知x为环上深度最小点,所以这个环的其余点,只要和x连边,都可以转移到x。
f[x]=max(f[v]+dis(x,v)) ,注意x和v在一个环内,这个式子就不难理解了。
问题是我们肯定不能够暴力枚举环内点,注意到dis(i,j),可以表示成min(j-i,n-i+j),为了去掉min,我们把环复制一遍,每次只考虑半个环长度的dp,然后就可以把式子化成 max(f[i]+f[j]+ji) ,那么现在单调队列处理 max(f[j]+j) ,这样就很简单了。
真是神题。。

#include
#include
#include
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=1e5+5;
const int M=5e6+5;
int n,m;
int a[N],head[N],next[M],go[M];
int low[N],dfn[N],vis[N],sta[N],tot,cnt;
int fa[N],dep[N],f[N],q[N],ans;
int read()
{
    int x=0;char ch=getchar();
    while (ch<'0'||ch>'9')ch=getchar();
    while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x;
}
inline void add(int x,int y)
{
    go[++tot]=y;
    next[tot]=head[x];
    head[x]=tot;
}
inline void dp(int x,int y)
{
    int tot=dep[y]-dep[x]+1;
    int t=1,w=1;
    for(int i=y;i!=x;i=fa[i])a[tot--]=f[i];
    a[1]=f[x];tot=dep[y]-dep[x]+1;
    q[1]=1;
    fo(i,1,tot)a[i+tot]=a[i];
    fo(i,2,2*tot)
    {
        if (i-q[t]>tot/2)t++;
        ans=max(ans,a[i]+i+a[q[t]]-q[t]);
        while (t<=w&&a[i]-i>=a[q[w]]-q[w])w--;
        q[++w]=i;
    }
    fo(i,2,tot)f[x]=max(f[x],a[i]+min(i-1,tot-i+1));
}
inline void dfs(int x)
{
    dfn[x]=low[x]=++cnt;
    for(int i=head[x];i;i=next[i])
    {
        int v=go[i];
        if (v==fa[x])continue;
        if (!dfn[v])
        {
            fa[v]=x;
            dep[v]=dep[x]+1;
            dfs(v);
        }
        low[x]=min(low[x],low[v]);
        if (low[v]>dfn[x])
        ans=max(ans,f[v]+f[x]+1),f[x]=max(f[x],f[v]+1);
    }
    for(int i=head[x];i;i=next[i])
    {
        int v=go[i];
        if (dfn[v]>dfn[x]&&fa[v]!=x)dp(x,v);
    }
}
int main()
{
    n=read(),m=read();
    fo(i,1,m)
    {
        int s=read(),last=0,x;
        fo(j,1,s)
        {
            x=read();
            if (last)add(last,x),add(x,last);
            last=x;
        }
    }
    dfs(1);
    printf("%d\n",ans);

}

你可能感兴趣的:(bzoj,DP,神奇脑洞题,单调队列,树形DP)