[ZJOI2008] 骑士

[ZJOI2008]骑士


题意简述

求基环树森林中的最大权独立集。

数据范围:\(1\le n\le 10^6\)


知识要点

  • 树形 DP
  • 基环树

题目分析

题目一上眼,是一个显然的最大权独立集问题。这是树形 DP 中选择节点类的题目,如果整个图构成一颗树的话,可以直接套路地定义状态 \(dp[i][0]/dp[i][1]\),表示第 \(i\) 个节点,是否选择,得到的以 \(i\) 为根节点的树中的最大权独立集。

容易写出状态转移方程:

\[\begin{aligned}\begin{cases} dp[i][0]&=\sum\limits_{x\in son[i]} \max\Big(dp[x][0],dp[x][1]\Big)\\dp[i][1]&=\sum\limits_{x\in son[i]}dp[x][0]\end{cases}\end{aligned}\]

边界条件:\(dp[i][0]=0,dp[i][1]=val[i]\)

但在此题中,我们需要在基环树森林中求最大权独立集。注意到图中仅有 \(n\) 个点 \(n\) 条边,因此图中有且仅有一个环。而断开这个环上的任意一条边,剩下的点和边就形成了一颗树,我们就可以用树形 DP 进行统计了。

要注意,虽然我们把边断开了,但是边两端的节点 \(u\)\(v\) 仍然不能同时选择。因此我们需要采取强制措施,对断边的两端 \(u\)\(v\) 都跑一次树形 DP,并且在从 \(u\) 开始时不经过 \(v\),从 \(v\) 开始时不经过 \(u\)

为了便于操作,我们从骑士的敌人向骑士连一条有向边,因为一名骑士只有一个自己最厌恶的骑士,因此连边后整张图就构成了基环外向树。因此当跑树形 DP 重新经过出发点 \(u\) 时,那么这条边的起点就是 \(v\),也就是不能选的节点。出现此种情况时,我们设置 \(dp[v][1]=-inf\),就可以防止选取点 \(v\) 了。

如何找到环?在这里借用一下 hkr04 洛谷题解 中的图片:

在找环时,当我们发现 7 号点没有被访问时,说明这棵树的答案仍为统计。我们从该点往回回到环上,最终到达了 2 号点。


代码展示

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

typedef long long ll;
const ll inf=0x3f3f3f3f3f3f3f3f;
const int M=1e6+5;

int n,fa[M],p0;
bool vis[M];
ll val[M],dp[M][2],ans;

struct edge
{
	int to,nxt;
}e[M];
int hd[M],cnt;
void add(int x,int y)
{
	e[++cnt]=(edge){y,hd[x]};
	hd[x]=cnt;
}

void dfs(int u)
{
	vis[u]=true;
	dp[u][0]=0,dp[u][1]=val[u];// 边界条件
	
	for(int i=hd[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==p0)dp[v][1]=-inf;// 强制控制v不选
		else
		{
			dfs(v);
			dp[u][0]+=max(dp[v][0],dp[v][1]);
			dp[u][1]+=dp[v][0];// 状态转移
		}
	}
}

void find_circle(int u)
{
	vis[u]=true;
	while(!vis[fa[u]])
	{
		u=fa[u];
		vis[u]=true;
	}
	p0=u,dfs(u);
	ll now=max(dp[u][0],dp[u][1]);
	u=fa[u];//执行该语句前有 vis[fa[u]]=true,因此u和fa[u]就是需要断边的两端
	p0=u,dfs(u);
	now=max(now,max(dp[u][0],dp[u][1]));
	ans+=now;
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%lld%d",&val[i],&fa[i]);
		add(fa[i],i);//注意从敌人连向自己
	}
	for(int i=1;i<=n;i++)
		if(!vis[i])//被统计过了就不搜了
			find_circle(i);
	printf("%lld",ans);
	return 0;
}
posted @ 2021-07-03 15:50  cyl06  阅读(34)  评论(0编辑  收藏  举报