1584 ZJOI2008 骑士

首先声明:下面是关于这道题的某位大佬博主的解答代码作解释,代码出处为1584 ZJOI2008 骑士(Bzoj1040 LOJ10162 LUOGU2607 省选/NOI-) 枚举组合数30分 类01背包树上DP10分 基环树有向图(强制不选某点正确性证明)100分_mrcrack的博客-CSDN博客

【题目描述】

原题来自:ZJOI 2008

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

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

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

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

为描述战斗力,我们将骑士按照 11 至 NN 编号,给每位骑士估计一个战斗力,一个军团的战斗力为所有骑士的战斗力之和。

【输入】

输入第一行包含一个正整数 NN,描述骑士团的人数;

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

【输出】

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

【输入样例】

3
10 2
20 3
30 1

【输出样例】

30

【提示】

数据范围与提示:

对于 30% 的数据,满足 N≤10N≤10;

对于 60% 的数据,满足 N≤100N≤100;

对于 80% 的数据,满足 N≤104N≤104 ;

对于 100% 的数据,满足 N≤106N≤106 ,且每名骑士的战斗力都是不大于 106106 的正整数。

#include <bits/stdc++.h>
#define maxn 1000010
#define LL long long
using namespace std;
int n,head[maxn],tot,fa[maxn],vis[maxn],save;
LL f[maxn][2],mx,c[maxn];
int root;
struct node{
	int to,next;
}e[maxn<<1];
void add(int u,int v){
	tot++,e[tot].to=v,e[tot].next=head[u],head[u]=tot;
}
void init(){
	int v,u;
	scanf("%d",&n);
	for(v=1;v<=n;v++){
		scanf("%lld%d",&c[v],&u);
		add(u,v);//这里则是父节点指向子结点了 
		fa[v]=u;//v讨厌的是u,这就能形成更类似树的结构,因为fa就只有一个!
	}
}
void dfs(int u){
	//首先考虑正常结构的(不成环),那么由于传入的root是整棵树的根,所以这棵子树都会被遍历掉 
	//而且注意下面还是有标记vis的,正常结构不用说计算就很正常,一目了然。 
	//至于所谓的不正常结构,即环则分开看,首先看第一次传进来的那个root,以它为根一直去遍历 
	//如果不遇到环的终点(也就是又遍历到自己了)的情况还是很正常的,而遇到了之后呢采用一个 
	//f[v][1]=-maxn;continue的方式,就是表示这一次是绝对不取root的,因为环上相邻两点必然是 
	//最多取一个,第一次就专门跑f[root][0]的值,而第二次再来dfs则是再认真跑个f[root][1],然 
	//后再让二者比较取到该连通块的最大价值 
	int i,v;
	f[u][1]=c[u],f[u][0]=0;//两次dfs对应的初始化 
	for(i=head[u];i;i=e[i].next){
		v=e[i].to;
		vis[v]=1;
		if(v==root){
			f[v][1]=-maxn;
			continue;
		}
		dfs(v);
		f[u][1]+=f[v][0];
		f[u][0]+=max(f[v][0],f[v][1]);
	}
}
void find_circle(int x){
	LL t=0;
	vis[x]=1;
	root=x;
	while(!vis[fa[root]]){
		//当然除了这些说的,还有一个很重要的条件就是可以证明一个连通块中最多其实只能出现一个环 
		//假设有两个环,那么连接两个环的那条路径首先肯定是单向的,那么肯定两个环肯定有一个上面 
		//在路上的点要同时讨厌两个人,因此矛盾! 
		root=fa[root];
		vis[root]=1;
	}
	//此时root已经变成最大祖先或者是环上的一个点了 
	//将root与fa[root]连线隔断。 
	dfs(root);
	t=max(f[root][0],f[root][1]);//第一次绝对只是选f[root][0],也就是说t=f[root][0]就行了 
	root=fa[root];//下次就是那个尾部的变成root了,那么根据上面的代码,在遇到这个尾部的时候就是直 
	//接continue了,那个f[root][1]是直接不会加上的,也就是说其实两次的v==root性质其实还是不尽相同的 
	//而下面再次比较f[root][0],f[root][1]时虽然那个f[root][0]是不一定对的,毕竟可能和那个尾部加不加 
	//是有关的,但是没事,因为那个t就已经是上次跑出来的正确的f[root][0]了! 
	//至此全部解答完毕,总之码主牛逼就是了!!!!! 
	dfs(root);
	t=max(t,max(f[root][0],f[root][1]));
	mx+=t;//基环树森林处理
}
int main(){
	init();
	for(int i=1;i<=n;i++)
		if(!vis[i])//基环树森林处理 
			find_circle(i); 
	printf("%lld\n",mx);
	return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值