树上取石子's 题解

来自信息学奥赛一本通T1818
题面描述:
Alice和Bob想比比谁能够收集到最多的石子数量。
Alice将石子分成了 n n n 堆(编号 1 … n 1…n 1n),并且规定了它们的选取顺序,刚好形成一颗有向树。在游戏过程中,两人从根节点开始,轮流取走石子(一次取一堆),当一个人取走结点 i i i 的石子后,另一个人只能从结点i的儿子节点中选取一个。当取到叶子结点时游戏结束。
然后两人会比较自己得到的石子数量。已知两人采用的策略不同,Alice希望在让Bob取得尽可能少的前提下,自己取得最多;而Bob希望在自己取得尽可能多的前提下,让Alice取得最少。在两人都采取最优策略的情况下,请你计算出游戏结束时两人的石子数量。
游戏总是Alice先取,保证只存在一组解。
有点好奇一堆博弈论中混入了一道树形dp???
既然是树形dp,那么我觉得yzx一定秒了这题吧,那就交给他了我先走了。
这题拿到秒出正解思路,然后代码调了一个多小时。。。
显然我们只要先统计每个点的入度,找到入度为0的点开始dfs即可。
dfs过程中要传递当前节点和当前节点深度,如果当前深度为奇数时,显然这里是Alice取的,那么下一个就是Bob取,考虑当前节点的儿子节点中 s u m [ 0 ] sum[0] sum[0] 最大的节点就是Bob接下来会取的,如果相等就比较 s u m [ 1 ] sum[1] sum[1] 找到较小的进行转移;如果当前深度为偶数时,显然这里是Bob取的,那么下一个就是Alice取,考虑当前节点的儿子节点中 s u m [ 0 ] sum[0] sum[0] 最小的节点就是Alice接下来会取的,如果相等就比较 s u m [ 1 ] sum[1] sum[1] 找到较大的进行转移。
转移结束后,再在 s u m sum sum 统计当前节点的贡献即可,注意因为你 s u m sum sum 开始全为0,那么转移的时候取min肯定会出问题,所以当前点的第一个儿子节点只要直接赋值给当前点然后接下来照上面的做法即可(开始没想到调了好久,后面特判由于写的地方不太好又调了好久)。
code:

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,num[N],ans=0,top[N],sum[2][N],din[N],k;
//sum[1/0][i]表示i的子树中Alice/Bob能取到的石子数 
struct lol {int x,y;} e[N];
void ein(int x,int y){
	e[++ans]=(lol){top[x],y};
	top[x]=ans;
}
void init(){
	scanf("%d",&n);
	for(int i=0;++i<=n;scanf("%d",&num[i]));
	for(int i=0,u,v;++i<n;scanf("%d%d",&u,&v),ein(u,v),++din[v]);
}
void dfs(int x,int dep){
	for(int i=top[x],y,fl=0;i;i=e[i].x){
		y=e[i].y;
		dfs(y,dep+1);
		fl? 
			dep&1?
				sum[0][y]>sum[0][x]?(
					sum[0][x]=sum[0][y],sum[1][x]=sum[1][y]):
					sum[0][y]<sum[0][x]?:
						sum[1][x]=min(sum[1][y],sum[1][x]):
				sum[0][y]<sum[0][x]?(
					sum[0][x]=sum[0][y],sum[1][x]=sum[1][y]):
					sum[0][y]>sum[0][x]?:
						sum[1][x]=max(sum[1][y],sum[1][x]):
			(sum[0][x]=sum[0][y],sum[1][x]=sum[1][y],fl=1);
	}
	sum[dep&1][x]+=num[x];
}
void work(){
	for(int i=0;++i<=n;)
		if(!din[i]){//找入度为0的点为初始点开始dfs 
			k=i;
			dfs(i,1);
			return ;
		}
}
void prin(){
	printf("%d %d",sum[1][k],sum[0][k]);
}
int main(){
	init();
	work();
	prin();
	return 0;
} 
感谢各位dalao捧场
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值