题解 [ZJOI2008]骑士

题目链接
如果它不是有 n n n条边,而是 n − 1 n-1 n1条边的话,它就是一棵树,也就是没有上司的舞会
现在只多了一条边,那它就是基环树。相当于多了一个环,其他的结构没有很大的变化。至于对于基环树的处理,一般可以这么处理:

  1. 找到环
  2. 将环断开,让它成为一棵树,对于断开边的两个端点分别进行树形dp。当然,需要注意一些关于这两个点的限制条件
  3. 将信息整合,更新答案

需要注意的是,这里可能不只是一棵树,要对每个块进行相同的处理并加入到答案中。还要注意数据范围问题,需要开 l o n g   l o n g long \ long long long
对于本题的一般树形态,我们将状态设计为 f [ i ] [ 0 / 1 ] f[i][0/1] f[i][0/1],表示对于以 i i i为根节点的子树,该节点不选/选的时候的点权和最大值。因为 i i i i i i的儿子不能同时被选中,所以 f [ i ] [ 0 ] = ∑ m a x ( f [ j ] [ 0 ] , f [ j ] [ 1 ] ) f[i][0]=\sum max(f[j][0], f[j][1]) f[i][0]=max(f[j][0],f[j][1])( j j j i i i的儿子),也即当前点的儿子不受限制; f [ i ] [ 1 ] = ∑ f [ i ] [ 0 ] f[i][1]=\sum f[i][0] f[i][1]=f[i][0],也即你选了我一定不能选我的儿子。对于一棵树的答案,就是 m a x ( f [ r o o t ] [ 0 ] , f [ r o o t ] [ 1 ] ) max(f[root][0], f[root][1]) max(f[root][0],f[root][1])
再来观察一下题目中基环树的形态,由于一个节点只会有一个仇恨的人,所以将我的仇人连向我(虽然成了仇人的儿子有点憋屈= =b)。这个时候,每个点有且仅有一个节点,建出来的一定是一棵外向树。如下图:
外向树
不会出现有两条边指向同一个点的情况:
Invalid
所以我们在找环的时候,一路回退,一定能找到当前基环树的环。
以上面那棵合法的举个例子,比如我发现7号点没有被访问过,这说明这棵树的答案我还没有统计,从这个点开始往回到环上。过程如下:

当发现当前点的父亲已被遍历,说明当前点和它的父亲都在这个环上,强行将这条边断开,分别将其作为根节点进行处理。又因为这两个不能同时选,我们干脆就在处理一个的时候,将另一个的 f [ i ] [ 1 ] f[i][1] f[i][1]设为负无穷。这样是不会影响最终答案的,是因为我们分别遍历的时候已经考虑了设为根节点的点选或不选的情况了,可以避免同时被选的非法情况。每处理一次,就尝试更新当前树的答案,处理完后将两种情况的最大值加入到总的答案中。
在向儿子节点遍历之前,先设好边界条件,也就是 f [ i ] [ 0 ] = 0 , f [ i ] [ 1 ] = w [ i ] f[i][0]=0,f[i][1]=w[i] f[i][0]=0,f[i][1]=w[i](当前点的点权)。记得你是要在一棵树中跑两次的,需要每次遍历之前都设好边界条件。

代码:

#include <cstdio>
typedef long long ll;
const int maxn=1000000+10;
const ll INF=1LL<<60;
int head[maxn],to[maxn],nxt[maxn],fa[maxn];
int tot;
int root;
ll w[maxn],f[maxn][2];
ll ans;
bool vis[maxn];

ll max(ll x,ll y) {return x>y?x:y;}
void add(int u,int v)
{
	nxt[++tot]=head[u];
	head[u]=tot;
	to[tot]=v;
}
void dfs(int u)
{
	vis[u]=1;
	f[u][0]=0,f[u][1]=w[u];
	for (int i=head[u];i;i=nxt[i])
	{
		int v=to[i];
		if (v!=root)
		{
			dfs(v);
			f[u][0]+=max(f[v][0], f[v][1]);
			f[u][1]+=f[v][0];
		}
		else
			f[v][1]=-INF;
	}
}
int FindCircle(int u)
{
	vis[u]=1;
	while(!vis[fa[u]])//因为入边唯一,这样一直往前找方便 
	{
		u=fa[u];
		vis[u]=1;
	}
	root=u; 
	dfs(u);
	ll tans=max(f[u][0], f[u][1]);
	u=fa[u];
	root=u;
	dfs(u);
	ans+=max(tans, max(f[u][0], f[u][1]));
}
int main()
{
	int n;
	scanf("%d",&n);
	for (int v=1;v<=n;v++)
	{
		int u;
		scanf("%lld%d",&w[v],&u);
		add(u, v);
		fa[v]=u;
	}
	for (int i=1;i<=n;i++)
		if (!vis[i])
			FindCircle(i);
	printf("%lld\n",ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值