1. 程式人生 > 實用技巧 >「AT2210 木の問題 / Problem on Tree」

「AT2210 木の問題 / Problem on Tree」

題目大意

在樹上按順序選出一些點,使得相鄰的兩點的樹上路徑中不存在其他被選的點,求可以選點的最大個數.

分析

這裡給出一種比較麻煩的非常暴力的做法.

考慮直接樹形 \(\operatorname{dp}\).首先可以發現對於一棵樹這棵樹的所有度為 \(1\) 的點必然是可以同時選擇的(這裡度指無根樹中一個點所連線的點的個數).有根樹比較難處理,下面假定 \(1\) 為根.那麼如果需要選擇一棵子樹中的點且選擇的這些點並不是在整個選擇點的開頭位置或者結尾位置,這顆子樹中第一個選擇的點前面存在點,最後一個選擇的點後面也有點,那麼這顆子樹必然是選擇葉節點最優(證明略,可以自行畫圖理解)現在設 \(f_i\)

表示當前這顆子樹不打算再上來的最多可選點數(對於開頭位置應為不打算下去,但是這兩種情況本質相同),為了表示方便設 \(g_i\) 表示以 \(i\) 為根的子樹中度為 \(1\) 的點的個數(根節點的度也可能為 \(1\)).

可以得到轉移方程:

\[f_i=\max\{g_i,f_j+1,f_j+g_i-g_j\} \]

(其中 \(j\)\(i\) 的子節點,\(f_j+1\) 表示選擇一個節點往下並且不上來,那麼顯然 \(i\) 這個點也可以選擇,\(f_j+g_i-g_j\) 表示選擇一個點向下,並且走完 \(i\) 的其他子樹的葉節點)

那麼答案也是分成兩種情況,一種是起點為根節點,也就是 \(f_i\)

,另一種情況比較麻煩,沒法確定起點和終點的位置,所以考慮在它們 \(\operatorname{LCA}\) 的位置計算答案,這裡還需要分成兩種情況,一種是選擇一個起點和一種終點,並且選擇了當前這個點(\(i\)),那麼就是

\[\max\{f_j+f_k+1\} \]

(其中 \(j,k\)\(i\) 的兩個不同的子節點)

這種情況可以選擇每個節點的子節點(\(j\))中 \(f_j\) 的最大值和次大值得出.

另一種情況為不選擇這個但是選擇這個點所連出去的度為 \(1\) 的節點,也就是:

\[\max\{f_j+f_k+g_1-g_i-g_j\} \]

這種情況可以選擇每個幾點的子節點(\(j\)

)中 \(f_j-g_j\) 的最大值和次大值得出.

這樣這題就做完了.

程式碼

#include<bits/stdc++.h>
#define REP(i,first,last) for(int i=first;i<=last;++i)
#define DOW(i,first,last) for(int i=first;last<=i;--i)
namespace IO
//快讀模板
using namespace IO;
using namespace std;
const int MAXN=1e6+5;
template<int MAXN,int MAXM>class Edge
{
    //存圖模板
	#define FOR(now_edge,now) //遍歷 now 的出邊
	#define TO(now_edge) //出邊
};
Edge<MAXN,MAXN<<1>e;
int n;
int f[MAXN],g[MAXN];
bool le[MAXN];//判斷一個節點是否為葉節點
int answer=0;
void DFS(int now=1,int father=0)//計算 f,g
{
	bool flag=1;
	FOR(e,now)
	{
		if(TO(e)^father)
		{
			flag=0;
			DFS(TO(e),now);
			g[now]+=g[TO(e)];
		}
	}
	le[now]+=flag;
	g[now]+=flag;
	f[now]=g[now];
	FOR(e,now)
	{
		if(TO(e)^father)
		{
			f[now]=Max(f[now],f[TO(e)]+g[now]-g[TO(e)],f[TO(e)]+1);//轉移
		}
	}
}
void DFS_2(int now=1,int father=0)//計算每個節點為 LCA 時的答案
{
	int max1=0,max2=0;//f[j] 的最大值和次大值
	int max3=0,max4=0;//f[j]-g[j] 的最大值和次大值
	FOR(e,now)
	{
		if(TO(e)^father)
		{
			DFS_2(TO(e),now);
			if(max1<f[TO(e)])
			{
				max2=max1;
				max1=f[TO(e)];
			}
			else
			{
				if(max2<f[TO(e)])
				{
					max2=f[TO(e)];
				}
			}
			if(max3<f[TO(e)]-g[TO(e)])
			{
				max4=max3;
				max3=f[TO(e)]-g[TO(e)];
			}
			else
			{
				if(max4<f[TO(e)]-g[TO(e)])
				{
					max4=f[TO(e)]-g[TO(e)];
				}
			}
		}
	}
	answer=Max(answer,max1+max2+1,g[1]+max3+max4);//統計兩種情況
}
int main()
{
	Read(n);
	int u,v,tot=0;
	REP(i,1,n-1)
	{
		Read(u,v);
		tot+=u==1||v==1;
		e[u]+=e[v]+=u;
	}
	if(tot==1)//特判根節點為葉節點的情況
	{
		le[1]=1;
		g[1]=1;
	}
	DFS();
	DFS_2();
	Writeln(Max(answer,f[1]));//輸出答案
	return 0;
}