ZOJ 3734: LIKE vs CANDLE

题目链接:

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3734


题目大意:

若干微博账户形成了一个转发树(即一个有根树)。

每个账户有自己的价值,每个账户也有自己的态度(赞或蜡烛)。

如果一个账户的态度是“赞”,它的价值就会被加到“赞”的一边,反之亦然。

Edward 可以从“赞”的一边拿出X 的价值去翻转一个账户,即把它的态度换到相反的一边。

但是Edward 发现,有的账户已经被别人翻转过了,对于这些账户,Edward 就要花费Y 的价值去翻转它们。

一旦一个账户被翻转了一次,它的所有子账户也会被翻转一次。

求“赞”的一边的价值总数与“蜡烛”一边的价值总数的最大差值。若最大差值为负数则输出“HAHAHAOMG”。


算法:

典型的树状DP。

但是如果直接自下向上更新的话,会发现父节点是否翻转的信息无法传递给子节点。

于是,除了记录当前节点是否直接被Edward 翻转以外,我们不妨再加一维状态,表示当前节点的父节点实际是否翻转(直接、间接都算在内)。


DP维护以下两个状态:

flag_pre 代表该节点的父节点实际是否被翻转(注意不是指父节点是否直接被Edward 翻转)

flag_cur 代表该节点是否直接被Edward 翻转(注意该节点直接被Edward 翻转不代表该节点最终一定被翻转了,因为有可能它之前被别人翻转过或者它的父节点被翻转了)


对于每个节点u,假设它的父节点是 p。

那么,它的 dp[u][0][x1]状态会被更新到dp[p][x2][0]状态上,它的 dp[u][1][x1]状态会被更新到dp[p][x2][1]状态上。

这样就保证了子节点和父节点间信息的一致性。


根据以上两个状态可以计算出以下两个状态

flag_nxt = flag_pre ^ flag_cur ^ s[i],代表该节点实际是否被翻转 。该节点的flag_nxt 对应的是它子节点的flag_pre。

flag_nxt ^ p[i],代表这个节点最终的态度(赞或者蜡烛)


其实只要维护这个点实际有没有翻转这一个状态就可以了,别的都是可以推出来的。

但是这样就要特判根节点,因为根节点不能翻转。我懒。。。


PS:

这道题在我写过的树状DP里算是代码非常之短的了。

50行您买不了吃亏~ 50行您买不了上当~ 耶!


代码如下:

#include <cstdio>
#include <climits>
#include <algorithm>
#include <vector>

const int maxn = 51000;
std::vector <int> map[maxn];
int v[maxn], f[maxn], s[maxn], p[maxn];
int dp[maxn][2][2];
int n, x, y;

int dfs(int u, int flag_pre, int flag_cur)
{
    if (dp[u][flag_pre][flag_cur] != INT_MIN)
    {
        return dp[u][flag_pre][flag_cur];
    }
    int flag_nxt = flag_pre ^ flag_cur ^ s[u];
    int tmp = (flag_nxt ^ p[u]) ? -v[u] : v[u];
    if (flag_cur)
    {
        tmp -= s[u] ? y : x;
    }
    for (int i = 0 ; i < map[u].size(); i ++)
    {
        int v = map[u][i];
        tmp += std::max(dfs(v, flag_nxt, 0), dfs(v, flag_nxt, 1));
    }
    return dp[u][flag_pre][flag_cur] = tmp;
}

int main()
{
    while (scanf("%d %d %d", &n, &x, &y) == 3)
    {
        for (int i = 0; i <= n; i ++)
        {
            map[i].clear();
            dp[i][0][0] = dp[i][0][1] = dp[i][1][0] = dp[i][1][1] = INT_MIN;
        }
        for (int i = 1; i <= n; i ++)
        {
            scanf("%d %d %d %d", &v[i], &f[i], &s[i], &p[i]);
            map[f[i]].push_back(i);
        }
        dfs(0, 0, 0) < 0 ? puts("HAHAHAOMG") : printf("%d\n", dp[0][0][0]);
    }
    return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值