2020.02.06日常总结——树上问题

d p \color{green}{树上dp}

  • 顾名思义,即树上的dp问题。
  • 有两种转移方式:一是从根转移到叶子,二是从叶子转移到根。在实际应用中,从叶子转移到根的方式应用的更多。
  • 树上dp在实现起来的时候,可能需要配合贪心等其它高效的算法,甚至于线段树等高效数据结构。
  • 代码实现起来的时候,常常用递归实现,因为树的定义就是递归定义的。同时,一定要注意不要从一个点又转移回了它的父亲,否则,一定会TLE
  • 因为树的点数 n n 经常是 1 0 5 10^5 级别的,所以一定要注意long long

\color{green}{基环树}

\color{blue}{【基础知识】:}
  • 基环树是一种特殊的图的结构。它由 n n 个点和 n n 条边组成,不仅全图强联通,而且只有一个环。
  • 根据树的特点和基环树的定义,我们可以发现基环树就是一棵树在加一条边。这是基环树很重要的一个性质。
  • 所以,一般解决基环树上问题时,我们需要找到环上一边,如何把它断掉,转化为树来求解。

在这里插入图片描述

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

\color{orange}{对于无根树:}

首先,像普通的树一样,从根到叶子访问,访问时记录每个点的父亲。

假设我们正在枚举点 u u ,枚举与u所有的相邻的点 v v ,如果 v v 已访问且 v v 不是 u u 的父亲,那么 ( u , v ) (u,v) 就是环上的一边。

注意,这种方法只能找一条,且时间复杂度为 O ( n + m ) O(n+m)

\color{orange}{对于有根树:}

我们可以随便指定一个顶点 u u ,从它开始,如果一个点 v v f a fa 没有被标记,我们先标记 f a ( v ) fa(v) ,如果将 v v 记为 f a ( v ) fa(v)

不断重复如上的算法,直到某个点 t t f a ( t ) fa(t) 已经被标记,那么 ( f a ( t ) , t ) (fa(t),t) 就是环上的一边。

在这里插入图片描述


P 2607     \color{green}{实例——洛谷P2607\ \ \ 骑士 }

\color{blue}{【题意】:} Z国骑士团是一个很有势力的组织,帮会中汇聚了来自各地的精英。他们劫富济贫,惩恶扬善,受到社会各界的赞扬。

最近发生了一件可怕的事情,邪恶的Y国发动了一场针对Z国的侵略战争。战火绵延五百里,在和平环境中安逸了数百年的Z国又怎能抵挡的住Y国的军队。于是人们把所有的希望都寄托在了骑士团的身上,就像期待有一个真龙天子的降生,带领正义打败邪恶。

骑士团是肯定具有打败邪恶势力的能力的,但是骑士们互相之间往往有一些矛盾。每个骑士都有且仅有一个自己最厌恶的骑士(当然不是他自己),他是绝对不会与自己最厌恶的人一同出征的。

战火绵延,人民生灵涂炭,组织起一个骑士军团加入战斗刻不容缓!国王交给了你一个艰巨的任务,从所有的骑士中选出一个骑士军团,使得军团内没有矛盾的两人(不存在一个骑士与他最痛恨的人一同被选入骑士军团的情况),并且,使得这支骑士军团最具有战斗力。

为了描述战斗力,我们将骑士按照 1 1 N N 编号,给每名骑士一个战斗力的估计,一个军团的战斗力为所有骑士的战斗力总和。

\color{blue}{【思路】:} 我们把每个骑士和他最痛恨的骑士之间连一条边,并把他最痛恨的骑士作为他的“父亲”。不难发现,这个模型是一棵有向基环树。

所以,我们枚举环上的边,把它断开,转化为树。用类似没有上司的舞会 d p dp 方法就可以完成。

注意数组别开太多,否则任意 M L E MLE 。注意常数不要太大,否则任意 T L E TLE

\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;
}
发布了103 篇原创文章 · 获赞 4 · 访问量 6733

猜你喜欢

转载自blog.csdn.net/ZHUYINGYE_123456/article/details/104199755