codeforces 538 E. Demiurges Play Again(树形dp)

题目链接

题目描述:
一棵n个结点的有根树,其中有m个叶结点,每个叶子结点上有一个数字
可以任意排列叶子上的数字(1~m)
根结点上有一个棋子,双方轮流操作,只能从父结点移动到子结点
游戏的得分就是叶子结点上的编号
玩家一希望最大化得分,玩家二希望最小化得分
求最大得分和最小得分

分析:
努力yy出了一种naive的方法:
显然,奇数层会被先手(最大化得分)控制,偶数层会被后手(最小化得分)控制
每个人一定会选择叶结点个数较少的子树进入,这样留给另一个人的选择就会尽量少
对于先手,我们可以把尽量大的值放到这个子树中
对于后手,我们就把尽量小的值放到这个子树中

后来想了想好像不是很完善,代码也不是很好完成


f[i] f [ i ] 表示到达结点 i i 能获得其子树中第几大的权值

先手一定会选择min(f[son]),安排权值的时候把最大的权值都放置到一棵子树上
轮到后手操作的时候,ta会尽可能选择排名较后的
而先手的目标就是使最小值最大,那么最好的方式就是平均分配
最后后手只能得到 f[son] ∑ f [ s o n ]

对于后手, f f 的维护也是一样的,用f[i]表示第几小即可

#include<cstdio>
#include<cstring>
#include<iostream>

using namespace std;

const int N=200005;
struct node{
    int y,nxt;
};
node way[N<<1];
int st[N],tot=0,leaves=0,f[N],n;

void add(int u,int w) {
    tot++;way[tot].y=w;way[tot].nxt=st[u];st[u]=tot;
    tot++;way[tot].y=u;way[tot].nxt=st[w];st[w]=tot;
}

void dfs_A(int now,int fa,int flag) {   //f 第几大 
    int ch=0;
    if (flag) {                         //先手局 
        f[now]=n;
        for (int i=st[now];i;i=way[i].nxt) 
            if (way[i].y!=fa) {
                dfs_A(way[i].y,now,flag^1);
                f[now]=min(f[now],f[way[i].y]);
                ch++;
            } 
    }
    else {
        f[now]=0;
        for (int i=st[now];i;i=way[i].nxt) 
            if (way[i].y!=fa) {
                dfs_A(way[i].y,now,flag^1);
                f[now]+=f[way[i].y];
                ch++;
            }
    }
    if (!ch) f[now]=1,leaves++;
}

void dfs_B(int now,int fa,int flag) {    //f 第几小 
    int ch=0;
    if (!flag) {
        f[now]=n;
        for (int i=st[now];i;i=way[i].nxt)
            if (way[i].y!=fa) {
                dfs_B(way[i].y,now,flag^1);
                f[now]=min(f[now],f[way[i].y]);
                ch++;
            }
    }
    else {
        f[now]=0;
        for (int i=st[now];i;i=way[i].nxt)
            if (way[i].y!=fa) {
                dfs_B(way[i].y,now,flag^1);
                f[now]+=f[way[i].y];
                ch++;
            }
    }
    if (!ch) f[now]=1;
}

int main() 
{
    scanf("%d",&n);
    for (int i=1;i<n;i++) {
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,y);
    }
    dfs_A(1,0,1);
    printf("%d ",leaves-f[1]+1);
    dfs_B(1,0,1);
    printf("%d\n",f[1]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值