HDU 6662 - Acesrc and Travel【树形DP + 换根】

Acesrc和旅行


 

问题描述

Acesrc是南京大学着名的旅游者之首。在这个暑假期间,他和张和刘一起将前往香港。有ñ 香港的景点,和 n - 1连接这些景点的双向观光巴士路线。他们决定乘公共汽车去一些景点。

然而,张和刘对这些景点有不同的偏好。他们分别为每个地点设定了令人满意的价值。如果他们访问了一世当场,张将获得满意的价值 一个一世,刘将获得 b一世。从张开始,为了公平起见,他们轮流决定下一个访问的地方。当前景点和下一个景点之间必须有一条公交线路。而且,他们永远不会两次去一个地方。如果有人找不到这样的下一个景点,他们别无选择,只能结束这次旅行。

张和刘都是超级聪明的竞争程序员。要么想要最大化他的总满意值与另一个满意度之间的差异。现在Acesrc想知道,如果他们都选择最佳,那么张和刘总的满意度之间有什么区别?

 

输入

第一行输入由一个整数组成 Ť (1 ≤ Ť≤ 30 ),表示测试用例的数量。

对于每个测试用例,第一行包含一个整数ñ (1 ≤ Ñ ≤ 10五),表示斑点的数量。接下来的两行中的每一行都包含ñ 整数, 一个1,a2,⋯ ,añ 和 b1,b2,⋯ ,bñ (0 ≤ 一一世,b一世≤ 109),分别表示
张和刘对每个地方的满意价值。最后一个n - 1 行包含两个整数 x ,y (1 ≤ X ,ÿ≤ Ñ ,X ≠ ÿ),表示之间的公共汽车路线 X现场和 ÿ现场。可通过这些巴士路线从任何地点到任何其他地点。

它保证了总和ñ 不超过 5.01 × 10五。

 

产量

对于每个测试用例,在一行中打印一个整数,如果它们都选择最佳,则总满意值之差。

 思路:对于每一个点的权值我们令其为ai - bi, 对于A来说,A想要这个值尽可能大。而对于B来说,B想要这个值尽可能小。

首先当根是固定的情况,我们令f[i][0] 表示第i个节点先手走的最大值,f[i][1]表示第i个节点后手走的最小值。

那么我们可以很容易的得到转移方程 f[u][0] = max(f[v][1]) + val[u], f[u][1] = min(f[v][0] ) + val[u] 。所以我们可以通过一遍DFS得到答案。

然而根并不确定,所以接下来我们考虑换根。在换根时之所以要记录一个次大和次小值g , 是因为对某个节点u来说,它的状态可能是从父亲转移过来的,也可能是从儿子转移过来的,在我们换根给儿子v的过程中,儿子v转移得到的可能是 之前它转移给父亲的,所以此时我们就不能再转移最值了,而是次大值(次小值)。

另外由于先手可以选择任意的点开始,所以要去max(f[k][1] ) ( 1 <= k <= n) ,这样对先手来说最优。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define ls rt << 1
#define rs rt << 1|1
#define lson l, mid, rt << 1
#define rson mid + 1, r, rt << 1|1
const int maxn = 1e5 + 10;
const ll inf = 0x3f3f3f3f3f3f3f3f;
ll val[maxn], f[maxn][2], g[maxn][2]; //每个结点的权值,最值和次值  0表示先手  1表示后手
vector<int> e[maxn];

void init(int n)
{
    for(int i = 0; i <= n; ++i)
    {
        e[i].clear();
        f[i][0] = g[i][0] = -inf;
        f[i][1] = g[i][1] = inf;
    }
}
void dfs(int u, int fa)
{
    for(auto v : e[u])
    {
        if(v == fa) continue;
        dfs(v, u);
        if(f[v][1] + val[u] > f[u][0]) //先手
            g[u][0] = f[u][0], f[u][0] = f[v][1] + val[u];
        else if(f[v][1] + val[u] > g[u][0])
            g[u][0] = f[v][1] + val[u];
        if(f[v][0] + val[u] < f[u][1]) //后手
            g[u][1] = f[u][1], f[u][1] = f[v][0] + val[u];
        else if(f[v][0] + val[u] < g[u][1])
            g[u][1] = f[v][0] + val[u];
    }
    if(e[u].size() == 1 && e[u][0] == fa) //叶子结点
        f[u][0] = f[u][1] = val[u];

}
void slove(int u, int fa)
{
    for(auto v : e[u])
    {
        if(v == fa) continue;
        ll Max = -inf, Min = inf;
        if(g[u][0] == -inf) Max = val[u]; //单链的情况
        else
        {
            Max = f[u][0];
            if(f[v][1] + val[u] == f[u][0]) //如果父亲的最优值是从儿子转移过来的那么只能用次大的
                Max = g[u][0];
        }
        if(g[u][1] == inf) Min = val[u];
        else
        {
            Min = f[u][1];
            if(f[v][0] + val[u] == f[u][1])
                Min = g[u][1];
        }
        if(e[v].size() == 1) //叶子结点
        {
            f[v][0] = Min + val[v];
            f[v][1] = Max + val[v];
            continue;
        } //对儿子进行转移
        if(Max + val[v] < f[v][1])
            g[v][1] = f[v][1], f[v][1] = Max + val[v];
        else if(Max + val[v] < g[v][1])
            g[v][1] = Max + val[v];
        if(Min + val[v] > f[v][0])
            g[v][0] = f[v][0], f[v][0] = Min + val[v];
        else if(Min + val[v] > g[v][0])
            g[v][0] = Min + val[v];
        slove(v, u);
    }
}

int main()
{
    int t;
    scanf("%d", &t);
    while(t--)
    {
        int n;
        scanf("%d", &n);
        init(n);
        for(int i = 1; i <= n; ++i)
            scanf("%lld", &val[i]);
        ll x;
        for(int i = 1; i <= n; ++i)
        {
            scanf("%lld", &x);
            val[i] -= x;
        }
        for(int i = 1; i < n; ++i)
        {
            int u, v;
            scanf("%d%d", &u, &v);
            e[u].push_back(v);
            e[v].push_back(u);
        }
        dfs(1, -1);
        slove(1, -1);
        ll ans = -inf;
        for(int i = 1; i <= n; ++i)
            ans = max(ans, f[i][1]);
        printf("%lld\n", ans);
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值