1. 程式人生 > 實用技巧 >基環樹DP

基環樹DP

基環樹

在圖論中,樹是一種簡單圖 \(G=(V,E)\) ,其中 \(|V|=|E|+1\) ,且不存在環。

它有著這樣的一個性質:如果在 \(G\) 中任意不相連兩點間加上一條邊,新圖 \(G'=(V,E')\) 正好含有一個環。

而基環樹就是從上述性質所得到的圖 \(G'\)

因此,我們知道一棵基環樹是由 \(n\) 個點和 \(n\) 條邊所構成的。

如果刪掉環上的一條邊,基環樹就退化成了樹。

基環樹DP

我們常常會遇到一類動態規劃的題目是在基環樹上進行的。

對於這類題目,我們一般有以下兩種做法:

  1. 列舉每一條邊,判斷刪去這條邊後判斷是否變成了一棵樹,若是,則進行樹形DP。當然與這條邊相關的資訊要特殊判斷。

  2. 我們先從這棵基環樹上找環,對於環上的每個節點所產生的內/外向樹進行樹形DP,然後再在環上進行一次環形DP即可。

例題選講

下面提供幾道較為基礎的題目:

例題1:[ZJOI2008]騎士

\(\large{\text{Description:}}\)

\(n\) 個騎士組成一個隊,每個騎士有他的戰力並恨一個人。

要求相互憎恨的人不同時在隊裡,怎樣安排戰鬥力最大?

輸出最大總戰鬥力即可。

\(\large{\text{Solution:}}\)

其實這題就是 沒有上司的舞會 的基環樹版本。

我們容易發現,\(n\) 個人中會存在多個聯通塊,而每個聯通塊有且只有一個環,即原圖是個基環樹森林。

於是在每棵基環樹中,我們先找出其中的環,

對於環上的每個點 \(x\) ,我們分別對以 \(x\) 為根的子樹中進行如下的樹形DP:

\(dp[u][0]\) 表示在以 \(u\) 為根的這棵子樹中不選節點 \(u\) 時最大的權值和; \(dp[u][1]\) 表示在以 \(u\) 為根的這棵子樹中必選節點 \(u\) 時最大的權值和。

那麼不難得出這樣的狀態轉移方程:

\[dp[u][0]=\sum_{(u,v)\in E}\max\{dp[v][0],dp[v][1]\} \]

\[dp[u][1]=\sum_{(u,v)\in E}dp[v][0]+val[u] \]

接下來我們考慮在環上的操作。

在這題中,我們只需考慮其中相鄰兩個點的選取情況即可。

也就是說,對於環上的任意相鄰兩點 \(u,v\) ,我們需要強制要求其中某點不被我們選取。

將它們之間斷邊,分別跑一次樹形DP即可。

實現起來就是: \(ans=ans+\max(dp[u][0],dp[v][0])\)

至此,本題已解決。

#include<bits/stdc++.h>
#define Re register
using namespace std;

typedef long long LL;

const int N=1000005;

struct Node {
	int ver,nxt;
}e[N<<1];
int cnt=1,hd[N];

int n,hte; LL a[N];

int U,V,E; LL dp[N][2],ans;

bool vis[N];

inline void add(int u,int v)
{
	cnt++;
	e[cnt].ver=v;
	e[cnt].nxt=hd[u];
	hd[u]=cnt;
}

inline void dfs1(int u,int f)
{
	vis[u]=1;
	for(Re int i=hd[u];i;i=e[i].nxt)
	{
		int v=e[i].ver;
		if((i^1)==f) continue;
		if(vis[v])
		{
			U=u,V=e[i].ver,E=i;
			continue;
		}
		dfs1(v,i);
	}
}

inline void dfs2(int u,int f)
{
	dp[u][0]=0;
	dp[u][1]=a[u];
	for(Re int i=hd[u];i;i=e[i].nxt)
	{
		int v=e[i].ver;
		if(i==E||(i^1)==E||(i^1)==f) continue;
		dfs2(v,i);
		dp[u][0]+=max(dp[v][0],dp[v][1]);
		dp[u][1]+=dp[v][0];
	}
}

int main()
{
	scanf("%d",&n);
	for(Re int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
		scanf("%d",&hte);
		add(hte,i);
		add(i,hte);
	}
	for(Re int i=1;i<=n;i++)
	{
		if(vis[i]) continue;
		dfs1(i,0);
		dfs2(U,0);
		LL res=dp[U][0];
		dfs2(V,0);
		ans+=max(res,dp[V][0]);
	}
	printf("%lld",ans);
	return 0;
}