P2607 [ZJOI2008]骑士

P2607 [ZJOI2008]骑士

题目描述

Z 国的骑士团是一个很有*的组织,帮会中汇聚了来自各地的精英。他们劫富济贫,惩恶扬善,受到社会各界的赞扬。

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

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

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

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

输入格式

第一行包含一个整数 \(n\),描述骑士团的人数。

接下来 \(n\) 行,每行两个整数,按顺序描述每一名骑士的战斗力和他最痛恨的骑士。

输出格式

应输出一行,包含一个整数,表示你所选出的骑士军团的战斗力。

输入输出样例

输入 #1

3
10 2
20 3
30 1

输出 #1

30

说明/提示

数据规模与约定

对于 30% 的测试数据,满足\(n \le 10\);

对于 60% 的测试数据,满足 \(n \le 100\);

对于 80% 的测试数据,满足\(n \le 10 ^4\)。


对于 100% 的测试数据,满足 \(1\le n \le 10^6\),每名骑士的战斗力都是不大于 \(10^6\) 的正整数。

首先,我们对于互相憎恨的骑士连一条双向边,然后我们就可以求全图的最大独立子集(只相邻的点不能同时被选)。

就转化为了类似于上司的舞会那道题。

就有转移 \(f[x][0] += max(f[to][0],f[to][1])\), \(f[x][1] += f[to][0]\)

\(f[x][0/1]\) 表示 在以 \(x\) 为根的子树中选或不选 \(x\) 节点得到的最大权值。

可这可能会出现环的情况,变成基环树或者基环树森林。

这,我们还是按照套路,找出每个环,然后断掉环上的一条边,在对整颗树做一遍树形\(dp\) 就可以得到答案。

一个比较好的优化就是 我们只需要枚举断掉的环上的两个端点,在对整棵树跑一边 \(dp\).

而不至于对每条边的端点都跑一遍。

因为当你这个点的状态确定的话,后面所有点的状态都会是确定的(可以感性理解一下)。

最后再说一下比较坑的一个点:

  • 找到环之后要 continue 而不是直接return ,因为你可能两个骑士互相憎恨,这两个点就成了一个环,

  • 然后又连接了其他的点,这样你就会 \(MLE\)

具体图例长这样:

P2607 [ZJOI2008]骑士

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define LL long long
const int N = 1e6+10;
int n,m,tot = 1,x,st,en,id;
int head[N],w[N];
LL f[N][2],ans,maxn;
bool vis[N];
inline int read()
{
	int s = 0,w = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
	return s * w;
}
struct node
{
	int to,net;
}e[N<<1];
void add(int x,int y)
{
	e[++tot].to = y;
	e[tot].net = head[x];
	head[x] = tot;
}
void find(int x,int from)//from 指从他来的编号
{
	vis[x] = 1;
	for(int i = head[x]; i; i = e[i].net)
	{
		int to = e[i].to;
		if((i ^ 1) == from) continue;//他不能是连向他父亲的边
		if(vis[to])
		{
			st = x;//记录一下删除的边的编号以及这条边的左右端点
			en = to;
			id = i;
			continue;//要把整棵树都搜完
//			return;
		}
		else find(to,i);
	}
}
void dp(int x,int from)//求最大独立集
{
	f[x][1] = w[x]; f[x][0] = 0;
	for(int i = head[x]; i; i = e[i].net)
	{
		int to = e[i].to;
		if((i ^ 1) == from || (i == (id ^ 1)) || i == id)  continue;//他不可以是被删除的边,或者是返祖边
		dp(to,i);
		f[x][1] += f[to][0];
		f[x][0] += max(f[to][1],f[to][0]);
	}
}
int main()
{
	n = read();
	for(int i = 1; i <= n; i++)
	{
		w[i] = read(); x = read();
		add(x,i); add(i,x);
	}
	for(int i = 1; i <= n; i++)
	{
		if(vis[i]) continue;//可能是基环树森林,要对每棵树都跑一边dp
//		printf("-------->\n");
		find(i,0); maxn = 0;//找环
//		cout<<st<<" "<<en<<" "<<id<<endl;
		dp(st,0); //枚举两个端点的状态
		maxn = max(maxn,f[st][0]);
		dp(en,0);
		maxn = max(maxn,f[en][0]);
		ans += maxn;//加上每棵树的答案
	}
	printf("%lld\n",ans);
	return 0;
}
上一篇:[ZJOI2008] 骑士 - 基环树dp


下一篇:BZOJ 1034. [ZJOI2008]泡泡堂BNB