题解 [ZJOI2008]骑士

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

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

需要注意的是,这里可能不只是一棵树,要对每个块进行相同的处理并加入到答案中。还要注意数据范围问题,需要开long longlong \ long
对于本题的一般树形态,我们将状态设计为f[i][0/1]f[i][0/1],表示对于以ii为根节点的子树,该节点不选/选的时候的点权和最大值。因为iiii的儿子不能同时被选中,所以f[i][0]=max(f[j][0],f[j][1])f[i][0]=\sum max(f[j][0], f[j][1])(jjii的儿子),也即当前点的儿子不受限制;f[i][1]=f[i][0]f[i][1]=\sum f[i][0],也即你选了我一定不能选我的儿子。对于一棵树的答案,就是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][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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章