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;
}