题意:给一颗仙人掌求直径。
经典好题。
一开始naive的想以为缩点以后直接求,想了想感觉自己是傻子。。块内的根本无法统计。
大概能想到DP求解,但是单调队列真心被震惊到了= =
设f[x],表示以x为起点(从上往下)的最长路径,对于树边/非树边分别转移,树边当让直接转移了,主要是非树边,非树边就是环上边,我只用环上的点更新f[x](x为环上深度最小点),这个需要DP。
树边情况,对于 f[x],有f[x]=max(f[v]+1),v∈son
非树边情况:
已知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]+j−i) ,那么现在单调队列处理 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);
}