23. 石子移动问题 | 豆包 MarsCode 刷题

119 阅读3分钟

我没有注意到任何不变量,于是我用最笨的办法做出了这道题。(不过做了三个小时。)

记石子数量为 nn

定义滚雪球操作:反复取当前位于最左(右)侧的石子,将其移动到最左(右)侧的合法位置上,称为左(右)侧滚雪球。左(右)侧滚雪球会使左(右)侧的石子形成位置连续的一段(以下称为雪球),且这一段的石子数量(以下称为雪球大小)不断增加。在 n3n\ge3 时,可以证明,如果不停止操作,左、右滚雪球最终都会得到大小为 nn 的雪球。

滚雪球实际上就是移动左侧或右侧石子的某种贪心策略。我们需要将左侧和右侧的贪心策略缝合起来。枚举分界点 x[0,n]x\in[0,\,n],对每个 xx,计算完成以下过程的最大操作次数 f(x)f(x)

  1. 在左侧滚出大小为 xx 的雪球。
  2. 在右侧滚出大小为 nxn-x 的雪球。
  3. 将两个雪球合并——即通过移动使两段石子最终靠在一起。

则答案为 maxf(x)\max f(x)

下面考虑如何计算 f(x)f(x)。在左、右侧分别进行一次彻底的滚雪球操作——即最终得到大小为 nn 的雪球。在滚雪球的过程中,记录 f1(x)f_1(x)f2(x)f_2(x),分别表示在左侧和右侧滚出大小为 xx 的雪球所需的最小操作次数。为什么不是最大?因为从最小操作次数增加到最大操作次数的过程,就是不断从雪球的一端取一个石子移动到另一端但不增加雪球大小的过程,这一过程的每一次操作都相当于将雪球整体移动,而这正是第 3 步“合并”过程所需的操作;因此,我们将达到最小操作次数——即得到了大小为 xx 的雪球——之后继续进行的操作全部视为第 3 步的操作。这么一来,上述三步中第 1、2 步的操作次数分别为 f1(x),f2(x)f_1(x),\,f_2(x),而第 3 步的最大操作次数为第 1、2 步结束后两个雪球之间的空位的数量。因此,如果我们还记录了左、右侧滚出大小为 xx 的雪球时雪球中紧邻中间的空位的石子的位置(分别记作 g1(x),g2(x)g_1(x),\,g_2(x)),我们便可计算 f(x)f(x),即 f(x)=f1(x)+f2(nx)+g2(nx)g1(x)1f(x)=f_1(x)+f_2(n-x)+g_2(n-x)-g_1(x)-1

时间复杂度:Θ(nlogn)\Theta(n\log n)(瓶颈为排序)。
空间复杂度:Θ(n)\Theta(n)

代码(C++):

#include <vector>
#include <algorithm>
using namespace std;

int solution(vector<int>& stones) {
    const int n = int(stones.size());
    if (n <= 2) {
        return 0;
    }
    sort(stones.begin(), stones.end());
    vector<int> snowballL_time(n + 1), snowballR_time(n + 1);
    vector<int> snowballL_pos(n + 1), snowballR_pos(n + 1);
    int pos;
    int consecutive0 = 0, consecutive1 = 1;
    while (consecutive0 < n - 1 &&
            stones[consecutive0 + 1] - stones[consecutive0] == 1) {
        ++consecutive0;
    }
    snowballL_pos[consecutive0 + 1] = pos = stones[consecutive0];
    while (consecutive1 < n - 1 &&
            stones[consecutive1 + 1] - stones[consecutive1] == 1) {
        ++consecutive1;
    }
    if (consecutive1 < n - 1) {
        int cur_time = 0, len = consecutive0 + 1;
        if (stones[consecutive1 + 1] - stones[consecutive1] > 2) {
            if (len == 1) {
                len = consecutive1 + 1;
                snowballL_time[len] = cur_time = 1;
                snowballL_pos[len] = pos = stones[consecutive1] + 1;
            }
        } else {
            len = consecutive1 + 1;
            pos = stones[consecutive1];
        }
        while (len < n) {
            cur_time += stones[len] - pos - 1;
            ++len;
            while (len < n && stones[len] - stones[len - 1] == 1) {
                ++len;
            }
            snowballL_time[len] = cur_time;
            snowballL_pos[len] = pos = stones[len - 1];
        }
    }
    consecutive0 = 0, consecutive1 = 1;
    while (consecutive0 < n - 1 &&
            stones[n - consecutive0 - 1] - stones[n - consecutive0 - 2] == 1) {
        ++consecutive0;
    }
    snowballR_pos[consecutive0 + 1] = pos = stones[n - consecutive0 - 1];
    while (consecutive1 < n - 1 &&
            stones[n - consecutive1 - 1] - stones[n - consecutive1 - 2] == 1) {
        ++consecutive1;
    }
    if (consecutive1 < n - 1) {
        int cur_time = 0, len = consecutive0 + 1;
        if (stones[n - consecutive1 - 1] - stones[n - consecutive1 - 2] > 2) {
            if (len == 1) {
                len = consecutive1 + 1;
                snowballR_time[len] = cur_time = 1;
                snowballR_pos[len] = pos = stones[n - consecutive1 - 1] - 1;
            }
        } else {
            len = consecutive1 + 1;
            pos = stones[n - consecutive1 - 1];
        }
        while (len < n) {
            cur_time += pos - stones[n - len - 1] - 1;
            ++len;
            while (len < n && stones[n - len] - stones[n - len - 1] == 1) {
                ++len;
            }
            snowballR_time[len] = cur_time;
            snowballR_pos[len] = pos = stones[n - len];
        }
    }
    int ans = max(snowballL_time[n], snowballR_time[n]);
    for (int i = 1; i < n; ++i) {
        if (snowballL_time[i] > 0 && snowballR_time[n - i] > 0) {
            ans = max(ans,
                snowballR_pos[n - i] - snowballL_pos[i] - 1 +
                snowballL_time[i] + snowballR_time[n - i]);
        }
    }
    return ans;
}