2020.02.06日常總結——樹上問題

dp\color{green}{樹上dp}

  • 顧名思義,即樹上的dp問題。
  • 有兩種轉移方式:一是從根轉移到葉子,二是從葉子轉移到根。在實際應用中,從葉子轉移到根的方式應用的更多。
  • 樹上dp在實現起來的時候,可能需要配合貪心等其它高效的算法,甚至於線段樹等高效數據結構。
  • 代碼實現起來的時候,常常用遞歸實現,因爲樹的定義就是遞歸定義的。同時,一定要注意不要從一個點又轉移回了它的父親,否則,一定會TLE
  • 因爲樹的點數nn經常是10510^5級別的,所以一定要注意long long

\color{green}{基環樹}

\color{blue}{【基礎知識】:}
  • 基環樹是一種特殊的圖的結構。它由nn個點和nn條邊組成,不僅全圖強聯通,而且只有一個環。
  • 根據樹的特點和基環樹的定義,我們可以發現基環樹就是一棵樹在加一條邊。這是基環樹很重要的一個性質。
  • 所以,一般解決基環樹上問題時,我們需要找到環上一邊,如何把它斷掉,轉化爲樹來求解。

在這裏插入圖片描述

\color{blue}{【如何找環】:}

\color{orange}{對於無根樹:}

首先,像普通的樹一樣,從根到葉子訪問,訪問時記錄每個點的父親。

假設我們正在枚舉點uu,枚舉與u所有的相鄰的點vv,如果vv已訪問且vv不是uu的父親,那麼(u,v)(u,v)就是環上的一邊。

注意,這種方法只能找一條,且時間複雜度爲O(n+m)O(n+m)

\color{orange}{對於有根樹:}

我們可以隨便指定一個頂點uu,從它開始,如果一個點vvfafa沒有被標記,我們先標記fa(v)fa(v),如果將vv記爲fa(v)fa(v)

不斷重複如上的算法,直到某個點ttfa(t)fa(t)已經被標記,那麼(fa(t),t)(fa(t),t)就是環上的一邊。

在這裏插入圖片描述


P2607   \color{green}{實例——洛谷P2607\ \ \ 騎士 }

\color{blue}{【題意】:} Z國騎士團是一個很有勢力的組織,幫會中匯聚了來自各地的精英。他們劫富濟貧,懲惡揚善,受到社會各界的讚揚。

最近發生了一件可怕的事情,邪惡的Y國發動了一場針對Z國的侵略戰爭。戰火綿延五百里,在和平環境中安逸了數百年的Z國又怎能抵擋的住Y國的軍隊。於是人們把所有的希望都寄託在了騎士團的身上,就像期待有一個真龍天子的降生,帶領正義打敗邪惡。

騎士團是肯定具有打敗邪惡勢力的能力的,但是騎士們互相之間往往有一些矛盾。每個騎士都有且僅有一個自己最厭惡的騎士(當然不是他自己),他是絕對不會與自己最厭惡的人一同出征的。

戰火綿延,人民生靈塗炭,組織起一個騎士軍團加入戰鬥刻不容緩!國王交給了你一個艱鉅的任務,從所有的騎士中選出一個騎士軍團,使得軍團內沒有矛盾的兩人(不存在一個騎士與他最痛恨的人一同被選入騎士軍團的情況),並且,使得這支騎士軍團最具有戰鬥力。

爲了描述戰鬥力,我們將騎士按照11NN編號,給每名騎士一個戰鬥力的估計,一個軍團的戰鬥力爲所有騎士的戰鬥力總和。

\color{blue}{【思路】:} 我們把每個騎士和他最痛恨的騎士之間連一條邊,並把他最痛恨的騎士作爲他的“父親”。不難發現,這個模型是一棵有向基環樹。

所以,我們枚舉環上的邊,把它斷開,轉化爲樹。用類似沒有上司的舞會dpdp方法就可以完成。

注意數組別開太多,否則任意MLEMLE。注意常數不要太大,否則任意TLETLE

\color{blue}{【代碼】:}

const int N=1e6+100;
#define ll long long
struct node{
	int next,to;
}e[N];int h[N],tot,n,fa[N];
inline void add(int a,int b){
	e[++tot]=(node){h[a],b};h[a]=tot;
}
int profit[N];ll f[N][2],ans;bool visit[N];
void dp(int u,int fa,int root){
	f[u][1]=profit[u];visit[u]=true;
	for(int i=h[u];i;i=e[i].next){
		register int to=e[i].to;
		if (to==root) continue;
		dp(to,u,root);f[u][1]+=f[to][0];
		f[u][0]+=max(f[to][0],f[to][1]);
	}
}
inline ll calc(int u){
	memset(f,0,sizeof(f));
	dp(u,-1,u);return f[u][0];
}

void find_circle(int u){
	visit[u]=true;int root=u;
	while (!visit[fa[root]]){
		visit[fa[root]]=true;
		root=fa[root];
	}
	ans+=max(calc(root),calc(fa[root]));
}
inline void initialization(){
	memset(visit,0,sizeof(visit));
	memset(profit,0,sizeof(profit));
	memset(h,0,sizeof(h));ans=tot=0;
}
int main(){
//	freopen("t1.in","r",stdin);
	n=read();initialization();
	for(int i=1;i<=n;i++){
		profit[i]=read();
		add(fa[i]=read(),i);
	}
	for(int i=1;i<=n;i++)
		if (!visit[i]) find_circle(i);
	printf("%lld",ans);
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章