leetcode 第 338 场算法比赛

作者: | 更新日期:

家里有事,没参加比赛。

本文首发于公众号:天空的代码世界,微信号:tiankonguse

零、背景

这次比赛听说最后一题很难。
由于我家里有事,没参加这次比赛。
周日回深圳的时候就很晚了,周一晚上发高烧,周二也有点低烧,还是有必要补上题解。

一、K 件物品的最大和

题意:三个数值(-1、0、1)分别给若干个,问选择 k 个数字的最大值。

思路:优先取 1,接着取 0,最后取 -1。

二、质数减法运算

题意:给一个数组,可以对任意位置的值减去任意素数,问是否可以使得数组变为严格递增数组。

思路:贪心:优先把当前数字减的尽量小,但大于上一个数字。

具体实现:先筛出素数表,然后遍历数组,按照贪心规则计算即可。

暴力寻找素数,复杂度:O(n^2)
二分查找素数,复杂度: O(n log(n))

三、使数组元素全部相等的最少操作次数

题意:给一个数组,每次操作可以对一个元素加一或减一,问最少需要多少次操作,可以使得所有数字值相等。

思路:离线加离散化扫描计算即可。

先对数组值和查询值进行离散化,数据量 10^5

先假设目标值都等于 0,答案就是数组和。
之后依次枚举离散化的值,并计算对应的答案。

计算转移公式:

ll dis = now - pre; // 上一个到当前值的距离
sum = sum - rightNum * dis + leftNum * dis
leftNum += counts[now];
rightNum -= counts[now];

四、收集树中金币

题意:给一个无向树,书中某些节点有金币。现在求一个子树,子树能覆盖所有金币。
覆盖定义:子树的每个顶点可以覆盖到距离为 2 以内的金币。
求节点数最小的子树。

思路:典型的树状 DP 问题,一个 DFS 搞定。

第一步:输入的边转为图。
第二步:递归,求出根为 0 时,各个节点的金币深度以及各个子树的最优答案。
第三步:DP 转移,将根节点从父节点转移到子节点,求出子节点为根的答案。

DP 转移分为的时候,需要把父节点的反向金币深度 和父节点的反向子树最优答案传下来。

void Dfs(const int now, const int pre = -1,
    const int preDep = 0, const int preAns = 0);

有了这个入参,子树为根的答案就可以直接计算出来。

childAns = max(1, coinAns0[now]) + preAns;

如下图,根转移到子树时,转移公式稍微有点复杂。

假设当前我们需要转移到 child 节点,就需要计算出 child 的 preDep 和 preAns。
而这个依赖与 now 节点的父节点子树 以及非 child 儿子节点,如上绿线圈起来的部分内容。

父节点的 preDep 和 preAns 函数参数中有,所以我们需要计算出非 child 儿子节点中的最大 dep 和累计答案。

暴力计算的话,可能会超时(根有 1 万个儿子)。
一般需要预处理出所有儿子的深度统计以及累计答案和,具体计算 child 的时候,再减去当前的 child 信息即可。

// 递归求子树的答案
Stat m;
int childSum = preAns;
for (auto v : g[now]) {
    if (v == pre) continue;

    m.Add(coinDeps[v]);
    childSum += coinAns0[v];
}


for (auto v : g[now]) {
    if (v == pre) continue;

    m.Del(coinDeps[v]);
    childSum -= coinAns0[v];

    int childMaxDep = preDep;
    if (m.Size() > 0) {
        childMaxDep = max(childMaxDep, m.QueryMax());
    }

    ...


    m.Add(coinDeps[v]);
    childSum += coinAns0[v];
}

当前根的其他儿子统计信息都得到了,就可以根据最大深度来判断了。
这里统计信息我使用 map 实现的。

大概分三种情况:
1、最大深度大于等于 2,此时根节点必须选择。
2、最大深度为0,此时根据当前根节点是否有金币判断。
3、最大深度为 1,则深度加一。

// 金币距离大于等于 2,必须选择当前根
if (childMaxDep == 0) {  // 长度为 0 或 1
    if (coins[now]) {
        childDep = 1;
    } else {
        childDep = 0;
    }
} else if (childMaxDep == 1) {
    childDep = childMaxDep + 1;
} else {
    childDep = 3;
    childAns = childSum + 1;
}

DfsAns(v, now, childDep, childAns);

五、最后

这次比赛最后一题有点难度,以往的树状DP都是很容易就状态转移,这次由于有两层的限制,转移稍微复杂一些。

思考题:2 层限制修改成 k 层,该如何做呢?

《完》

-EOF-

本文公众号:天空的代码世界
个人微信号:tiankonguse
公众号ID:tiankonguse-code

本文首发于公众号:天空的代码世界,微信号:tiankonguse
如果你想留言,可以在微信里面关注公众号进行留言。

关注公众号,接收最新消息

tiankonguse +
穿越