[SHOI2006]仙人掌

[SHOI2006]仙人掌

简要解析

其实很简单
只要普通树形 \(dp\) 就行了
\(f_x\) 表示 \(x\) 能向下延深的最大距离,\(v\)\(x\) 的儿子
当一个点不属于任何环时 \(f_x = \max(f_v + 1)\)
这是更新 \(ans = \max(ans , f_x + f_v + 1)\)
只是带环的话,环要单独算
这是我们的直径可以不经过环顶端的点,直接选环中两个点 \(u,v\)
\(ans = \max(ans , f_u + f_v + dist_{u,v})\)
显然不能 \(n^2\) 枚举这两个环中点
因为要符合最短路,所以这两个点距离 \(dist_{u,v} \leq lim\)\(lim\) 为环长的一半
那么我们可以再 \(dp\)\(f_u + f_v + dist_{u,v}\)
\(u,v\) 规定方向,从离环顶距离小的往大
于是破环成链再倍长,单调队列维护

\(Code\)

#include<cstdio>
#include<iostream>
using namespace std;

const int N = 100005;
int n , m , tot , dfc;
int h[N] , dfn[N] , low[N] , fa[N] , f[N] , a[N] , q[N] , ans;

struct edge{
	int to , nxt;
}e[N << 1];
void add(int x , int y){e[++tot] = edge{y , h[x]} , h[x] = tot;}

void solve(int x , int v)
{
	int lim , cnt = 0 , h = 1 , r = 1;
	for(register int i = v; i != fa[x]; i = fa[i]) a[++cnt] = f[i];
	for(register int i = 1; i <= cnt; i++) a[i + cnt] = a[i];
	lim = cnt >> 1 , q[1] = 1;
	for(register int i = 2; i <= cnt * 2; i++)
	{
		while (h < r && i - q[h] > lim) h++;
		ans = max(ans , i - q[h] + a[i] + a[q[h]]);
		while (r >= h && a[q[r]] - q[r] <= a[i] - i) r--;
		q[++r] = i;
	}
	for(register int i = 1; i <= cnt; i++) f[x] = max(f[x] , a[i] + min(i , cnt - i));
}

void tarjan(int x)
{
	dfn[x] = low[x] = ++dfc;
	int v;
	for(register int i = h[x]; i; i = e[i].nxt)
	{
		v = e[i].to;
		if (v == fa[x]) continue;
		if (!dfn[v]) fa[v] = x , tarjan(v) , low[x] = min(low[x] , low[v]);
		else low[x] = min(low[x] , dfn[v]);
		if (low[v] > dfn[x])
		{
			ans = max(ans , f[x] + f[v] + 1);
			f[x] = max(f[x] , f[v] + 1);
		} 
	}
	for(register int i = h[x]; i; i = e[i].nxt)
	if (fa[v = e[i].to] != x && dfn[v] > dfn[x]) solve(x , v);
}

int main()
{
	scanf("%d%d" , &n , &m);
	int num , x , y;
	for(register int i = 1; i <= m; i++)
	{
		scanf("%d%d" , &num , &x);
		for(register int j = 2; j <= num; j++) 
			scanf("%d" , &y) , add(x , y) , add(y , x) , x = y;
	}
	tarjan(1);
	printf("%d" , ans);
} 
posted @ 2020-09-09 20:18  leiyuanze  阅读(114)  评论(0编辑  收藏  举报