【东方幻想乡系列模拟赛 Stage 4】 秋静叶&秋穣子

Description

在幻想乡,秋姐妹是掌管秋天的神明,作为红叶之神的姐姐静叶和作为丰收之神的妹妹穰子。如果把红叶和果实联系在一起,自然会想到烤红薯。烤红薯需要很多的叶子,才能把红薯烤得很香,所以秋姐妹决定比比谁能够收集到最多的红叶。静叶将红叶分成了N堆(编号1..N),并且规定了它们的选取顺序,刚好形成一颗有向树。在游戏过程中,两人从根节点开始,轮流取走红叶,当一个人取走节点i的红叶后,另一个人只能从节点i的儿子节点中选取一个。当取到某个叶子时游戏结束,然后两人会比较自己得到的红叶数量。 
已知两人采用的策略不一样. 

    静叶考虑在让穰子取得尽可能少的前提下,自己取的最多;     

    而穰子想得是在自己尽可能取得多的前提下,让静叶取得最少。 

在两人都采取最优策略的情况下,请你计算出游戏结束时两人的红叶数量。 游戏总是静叶先取,保证只存在一组解。

Input

第1行:1个正整数N,表示红叶堆数 
第2行:N个整数,第i个数表示第i堆红叶的数量num[i] 
第3..N+1行:2个正整数u,v,表示节点u为节点v的父亲

Output

第1行:2个整数,分别表示静叶取到的叶子数和穰子取到的叶子数

Sample Input

6 
4 16 16 5 3 1 
1 2 
2 4 
1 3 
3 5 
3 6 

Sample Output

7 16 

Hint

首先静叶一定能取得节点1的4片红叶,留给穰子的是节点2和3,均为16片红叶。若选取 
节点2则静叶下一次可以最多得到5片红叶,而选择3静叶最多也只能得到3片红叶,所以 
此时穰子会选择节点3,故静叶最后得到的红叶数为7,穰子为16。 

对于30%的数据:1 ≤ N ≤ 100,1 ≤ num[i] ≤ 100 
对于60%的数据:1 ≤ N ≤ 10,000,1 ≤ num[i] ≤ 10,000 
对于100%的数据:1 ≤ N ≤ 100,000,1 ≤ num[i] ≤ 10,000 

保证两人得到的红叶数在[0, 2^31-1]。 

注意使用手工栈


 【分析】

博弈论

        在一棵有向树上最大-最小博弈。注意先后手的规则是不一样的。先手尽量让对方取少,而后手尽量让自己取多。注意要记录一个depth,以区分这个点是奇数还是偶数来判断先后手取得,以使用不同的规则。

        我们定义一个数组F[MAXN][2],F[i][0]表示以i为根的子树的先手最优值,F[i][1]表示以i为根的子树的后手最优值。

对于depth&1==1的情况:

        F[i][0]=Num[i]+F[k][1];

        F[i][1]=F[k][0];

k是i的儿子,k为max{F[k][0]}取得最大时的k,F[k][0]相同时,取F[k][1]最小的

即我是先手(全局的先手),我只能取奇数深度的根节点,然后我的对手便会按照他的准则来取。他的准则便是让自己尽量多,所以他会在保证F[k][0](他是全局的后手,那么他便是以k为根子树的先手)最大的情况下,来让F[k][1]最小,即让我尽量少。


对于depth&1==0的情况:

同样地:F[i][0]=Num[i]+F[k][1];

                F[i][1]=F[k][0];

k是i的儿子,k为min{F[k][1]}取得最小时的k,F[k][1]相同时,取F[k][0]最大的

即我是后手(全局的后手),我只能取偶数深度的根节点,而当前节点深度正好为偶数。我对手的准则让我尽量少,所以他会在保证F[k][1]尽量小的情况下(我是全局后手,又k为奇数节点,所以我为k的后手),让F[k][0]尽量大,即让我的对手尽量多。


【代码】

/*
    ID:Ciocio
	LANG:C++
	DATE:2013-11-23
	TASK:aki
*/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#include <utility>
#include <functional>
#include <map>

using namespace std;

#define MAXN 100005
#define INF 999999999

int N,Num[MAXN];
int Y[MAXN],Next[MAXN],Last[MAXN],rdu[MAXN],cdu[MAXN];
int F[MAXN][2];

int tot;
void _addedge(int a,int b)
{
	tot++;
	Y[tot]=b;
	Next[tot]=Last[a];
	Last[a]=tot;
	rdu[b]++;
	cdu[a]++;
}

void _read(int &x)
{
	char tt=getchar();
	while(tt<'0'||'9'<tt) tt=getchar();
	for(x=0;'0'<=tt&&tt<='9';x=x*10+tt-'0',tt=getchar());
}

void _init()
{
	_read(N);
	for(int i=1;i<=N;i++)
		_read(Num[i]);
	for(int i=1;i<N;i++)
	{
		int a,b;
		_read(a);_read(b);
		_addedge(a,b);
	}
}

typedef pair<int,int> pii;
stack <pii> S;
int Con[MAXN];
void _Treedp(int x,int depth)
{
	while(!S.empty()) S.pop();
	S.push(make_pair(x,depth));
	while(!S.empty())
	{
		x=S.top().first;
		depth=S.top().second;
	    if(!cdu[x])
	    {
		    F[x][0]=Num[x];
		    F[x][1]=0;
			S.pop();
			continue;
	    }
		int begin=Con[x]?Next[Con[x]]:Last[x];
		bool mark=false;
	    for(int j=begin;j;j=Next[j])
		{
			S.push(make_pair(Y[j],depth+1));
			Con[x]=j;mark=true;
			break;
		}
		if(mark) continue;
	    int k;
	    if(depth&1)   //情况一
	    {
		    int Max=-1;
		    for(int j=Last[x];j;j=Next[j])
		    {
			    if(F[Y[j]][0]>Max)   //Y[j]为儿子节点,对手选儿子节点中先手最大
			        Max=F[Y[j]][0],k=Y[j];
		        else if(F[Y[j]][0]==Max&&F[k][1]>F[Y[j]][1])  //后手最小
			    	k=Y[j];
		    }
		    F[x][0]=Num[x]+F[k][1];
		    F[x][1]=F[k][0];
	    }
	    else    
	    {
		    int Min=INF;
		    for(int j=Last[x];j;j=Next[j])
		    {
		    	if(F[Y[j]][1]<Min)       //对手选儿子节点中后手最小
			    	Min=F[Y[j]][1],k=Y[j];
			    else if(F[Y[j]][1]==Min&&F[k][0]<F[Y[j]][0])  //先手最大
			    	k=Y[j];
		    }
		    F[x][0]=Num[x]+F[k][1];
		    F[x][1]=F[k][0];
	    }
		S.pop();
	}
}

void _solve()
{
	int root;
	for(int i=1;i<=N;i++)
		if(!rdu[i])
		{
			root=i;
			break;
		}
	_Treedp(root,1);
	cout<<F[root][0]<<" "<<F[root][1]<<endl;
}

int main()
{
	_init();
	_solve();
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值