我說這是我們的noip互測題你信嗎...
首先介紹一下仙人掌(略,參見題面)
然後我們思考一下怎麼做:
首先,如果原圖是一棵樹,那麼做法是很顯然的(樹上最長鏈嘛)
但是,圖是一個仙人掌,所以樹上最長鏈的做法有bug
所以我們考慮:是否能將樹上的做法移接到仙人掌上即可
怎麼移接?
我們看到,根據仙人掌的性質,如果我們對這個仙人掌搜出一棵dfs樹,那麼不在環上的邊一定是樹邊
如果換一種說法,那麼這種邊一定是割邊!
所以,如果我們把仙人掌看做樹上掛着環的一種圖,那麼我們是可以套用樹上最長鏈的思想,配合樹形dp來解決這道題的!
舉個例子:
這是一棵很典型的樹
現在我引入了兩條綠色的邊(非樹邊),他就變成了一個典型的仙人掌
於是我們可以對這個仙人掌進行tarjan(很顯然,它是有環的,不是嗎?)
在tarjan的同時,我們對樹邊進行樹形dp
記dp[i]表示以i爲根節點且一定經過i的子樹中的以i爲起點的最長鏈長度
於是我們顯然有轉移:,其中to爲i的一個子節點
可是由於它是一個仙人掌,所以存在環,我們知道,對於環,樹形dp是處理不了的啊
所以我們藉助tarjan進行縮點,分別處理環內和環外的點
方式:對每個點記錄一個樹上父節點,那麼如果從某個點能直接連通到另一個點,但這個點卻不是那個點的樹上兒子,則說明這兩點之間一定存在一個環!
(這一點很顯然,對照圖理解一下就好)
接下來,在環內我們需要單獨處理一遍dp
處理方式待會再說
於是這道題就被分成了兩部分:
①:對樹部分進行dfs樹形dp
②:對環部分單獨dp
在樹部分,結合上面提到的轉移,我們有:
(更新答案是很顯然的,因爲我可沒有要求答案的起點一定是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; }