天天看点

树上博弈

一棵点权树,两人从根开始,一次选点,每个人只能选择上一个选点的子节点,两人策略不同。先手:让后手尽可能小前提下自己取值尽可能大;后手:自己取值尽量大的前提下让后手取值尽量小。先手从根开始取值,求先手与后手取值最终分别为多少。

关键词:树上博弈、最大-最小博弈

做法:由于决策考虑到先手和后手问题,因此必须增加一维状态,分别存储后手和先手的取值。

F[MAXN][2],F[i][0]表示以i为根的子树的先手最优值,F[i][1]表示以i为根的子树的后手最优值。

技巧:深度为奇数:先手;深度为偶数:后手

#include<stdio.h>
#include<iostream>
#include<string.h>
#define ll long long
#define maxn 1000010
#define INF 0x3f3f3f3f
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;

int n;
int num[maxn],du[maxn];
struct Edge{
    int to,next;
}edge[maxn];
int head[maxn],tot,root;
int dp[maxn][3];

void add(int u,int v){
    edge[tot].to=v,edge[tot].next=head[u],head[u]=tot++;
}

void dfs(int u,int d){
    int tmp1=-100,k=-1,tmp2=INF;
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        dfs(v,d+1);
        if(d&1){
            if(dp[v][0]>tmp1||(dp[v][0]==tmp1&&dp[v][1]<tmp2)) { tmp1=dp[v][0],tmp2=dp[v][1],k=v; }
        }
        else{
            if(dp[v][1]<tmp2||(dp[v][1]==tmp2&&dp[v][0]>tmp1)) { tmp2=dp[v][1],tmp1=dp[v][0],k=v; }
        }
    }
    dp[u][0]=num[u]+dp[k][1],dp[u][1]=dp[k][0];
}

int main(){
    //freopen("a.txt","r",stdin);
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&num[i]);
    mem(head,-1),tot=0,mem(du,0);
    for(int i=0;i<n-1;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        du[b]++;
        add(a,b);
    }
    for(int i=1;i<=n;i++) if(!du[i]) { root=i,dfs(i,1);break; }
    printf("%d %d\n",dp[root][0],dp[root][1]);
}