bzoj 1023

我說這是我們的noip互測題你信嗎...

首先介紹一下仙人掌(略,參見題面)

然後我們思考一下怎麼做:

首先,如果原圖是一棵樹,那麼做法是很顯然的(樹上最長鏈嘛)

但是,圖是一個仙人掌,所以樹上最長鏈的做法有bug

所以我們考慮:是否能將樹上的做法移接到仙人掌上即可

怎麼移接?

我們看到,根據仙人掌的性質,如果我們對這個仙人掌搜出一棵dfs樹,那麼不在環上的邊一定是樹邊

如果換一種說法,那麼這種邊一定是割邊!

所以,如果我們把仙人掌看做樹上掛着環的一種圖,那麼我們是可以套用樹上最長鏈的思想,配合樹形dp來解決這道題的!

舉個例子:

這是一棵很典型的樹 

 現在我引入了兩條綠色的邊(非樹邊),他就變成了一個典型的仙人掌

於是我們可以對這個仙人掌進行tarjan(很顯然,它是有環的,不是嗎?)

在tarjan的同時,我們對樹邊進行樹形dp

記dp[i]表示以i爲根節點且一定經過i的子樹中的以i爲起點的最長鏈長度

於是我們顯然有轉移:dp[u]=max(dp[u],dp[to]+1),其中to爲i的一個子節點

可是由於它是一個仙人掌,所以存在環,我們知道,對於環,樹形dp是處理不了的啊

所以我們藉助tarjan進行縮點,分別處理環內和環外的點

方式:對每個點記錄一個樹上父節點,那麼如果從某個點能直接連通到另一個點,但這個點卻不是那個點的樹上兒子,則說明這兩點之間一定存在一個環!

(這一點很顯然,對照圖理解一下就好)

接下來,在環內我們需要單獨處理一遍dp

處理方式待會再說

於是這道題就被分成了兩部分:

①:對樹部分進行dfs樹形dp

②:對環部分單獨dp

在樹部分,結合上面提到的轉移,我們有:

ans=max(ans,dp[u]+dp[to]+1)

dp[u]=max(dp[u],dp[to]+1)

(更新答案是很顯然的,因爲我可沒有要求答案的起點一定是u,所以自然是兩條以u爲起點的鏈通過u連起來比較長)

至於環內部分,結合我們剛纔提到的判環條件,我們能很清楚的發現一件事情:

①:對於一個環內點的dp值只會影響環內點,而不會影響環外點(環外點與環內點是通過樹邊進行更新,不涉及環的問題)

②:但是上面這句話存在漏洞:要求這個環內點並不是環中的最高點才行!

爲什麼?

例:

觀察一下,我們能看到:底下綠色的環的dp值只有最上面的那個點纔回涉及到對上半部分dp值的更新,而剩下的是沒有用的

所以我們在處理每個環時,僅需處理深度最淺的點,更新他的dp值即可

但是,每個點的dp值都會對答案有貢獻,因此不要忘記更新答案!

接下來的問題就好說了:如果我們記環中最高點爲u,那麼根據上述提到的找環的方法,我們完全可以:找到u的一個to,反覆找到to的父節點,根據u爲環中最高點這一性質,我們最終一定能跳到u,而所有遍歷到的點就是一整個環!

在更新答案時,顯然我們要找到換上兩點i,j,使得dp[i]+dp[j]+dis(i,j)最大來更新ans

樸素來看,這將是個O(n^2)算法

但是我們可以利用單調隊列進行優化,因爲dis(i,j)根據遍歷環的順序直接可求

這樣就優化成了O(n)

最後更新一遍環上最高點的dp值即可。

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
using namespace std;
struct Edge
{
	int next;
	int to;
}edge[300005];
int head[100005];
int dfn[100005];
int dep[100005];
int low[100005];
int f[100005];
int dp[100005];
int sta[100005],que[100005];
int tot,deep;
int cnt=1;
int n,m;
int ans=0;
void init()
{
	memset(head,-1,sizeof(head));
	cnt=1;
}
void add(int l,int r)
{
	edge[cnt].next=head[l];
	edge[cnt].to=r;
	head[l]=cnt++;
}
void dpit(int ed,int st)
{
	int cct=0;
	while(st!=ed)
	{
		sta[++cct]=dp[st];
		st=f[st];
	}
	sta[++cct]=dp[ed];
	for(int i=1;i<cct;i++)
	{
		sta[i+cct]=sta[i];
	}
	int head=1,tail=1;
	que[1]=1;
	for(int i=2;i<=cct+cct/2;i++)
	{
		while(head<=tail&&i-que[head]>cct/2)
		{
			head++;
		}
		ans=max(ans,sta[i]+sta[que[head]]+i-que[head]);
		while(head<=tail&&sta[que[tail]]+i-que[tail]<=sta[i])
		{
			tail--;
		}
		que[++tail]=i;
	}
	for(int i=1;i<cct;i++)
	{
		dp[ed]=max(dp[ed],sta[i]+min(i,cct-i));
	}
}
void tarjan(int rt)
{
	dfn[rt]=low[rt]=++deep;
	for(int i=head[rt];i!=-1;i=edge[i].next)
	{
		int to=edge[i].to;
		if(to==f[rt])
		{
			continue;
		}
		if(!dfn[to])
		{
			f[to]=rt;
			dep[to]=dep[rt]+1;
			tarjan(to);
			low[rt]=min(low[rt],low[to]);
			if(dfn[rt]<low[to])
			{
				ans=max(ans,dp[rt]+dp[to]+1);
				dp[rt]=max(dp[rt],dp[to]+1);
			}
		}else
		{
			low[rt]=min(low[rt],dfn[to]);
		}
		
	}
	for(int i=head[rt];i!=-1;i=edge[i].next)
	{
		int to=edge[i].to;
		if(f[to]==rt||dfn[to]<=dfn[rt])
		{
			continue;
		}
		dpit(rt,to);
	}
}
inline int read()
{
	int f=1,x=0;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int main()
{
//	freopen("pianfen.in","r",stdin);
//	freopen("pianfen.out","w",stdout);
	n=read(),m=read();
	init();
	for(int i=1;i<=m;i++)
	{
		int k=read();
		int las=0;
		for(int j=1;j<=k;j++)
		{
			int x=read();
			if(!las)
			{
				las=x;
				continue;
			}
			add(x,las);
			add(las,x);
			las=x;
		}
	}
	/*for(int i=1;i<=min(n,9871);i++)
	{
		int x=read();
		if(x!=0&&x!=1)
		{
			printf("-l\n");
			return 0;
		}
	}*/
	tarjan(1);
	printf("%d\n",ans);
	return 0;
}