【问题标题】:array median transformation minimum steps数组中值变换最小步数
【发布时间】:2012-05-08 13:07:39
【问题描述】:

给定一个数组 A 和 n 整数。在一个回合中,一个人可以申请 以下操作到任何连续 子数组 A[l..r] :分配给所有 A i (l

由于N的最大值是30。我想到了暴力破解结果 有没有更好的解决方案。

【问题讨论】:

  • 最少需要多少操作来改变什么?给定数组,还是任何给定大小的数组?
  • @n.m.,显然,一个给定的数组。因为对于任何数组,在最坏的情况下,最小数量总是 ceil(log N))。
  • @Evgeny:也许这就是预期的答案。或者也许是别的东西。你可以查看数组元素吗?
  • 数组 2 1 1 2 的一些示例答案为 1 .for 数组 4 4 4 4 4 的答案为 0。

标签: algorithm


【解决方案1】:

您可以在每次迭代中将包含最大元素的子数组的大小加倍。在第一次迭代之后,有一个大小为 2 的子数组包含最大值。然后将您的操作应用于包含这两个元素的大小为 4 的子数组,为您提供包含最大值的大小为 4 的子数组。然后应用于大小为 8 的子数组,依此类推。您在 log2(N) 操作中填充数组,这是最佳的。如果 N 为 30,则 5 次操作就足够了。

这在最坏的情况下是最优的(即只有一个元素是最大值时),因为它在每次迭代中设置了尽可能多的元素。

更新 1:我注意到我把 4s 和 8s 搞砸了。已更正。

更新 2:这是一个示例。数组大小10,开始状态:

[6 1 5 9 3 2 0 7 4 8]

要获得两个 9,请在包含 9 的大小为 2 的子数组上运行 op。例如 A[4…5] 让你:

[6 1 5 9 9 2 0 7 4 8]

现在在包含 4…5 的大小为 4 的子数组上运行,例如在 A[2…5] 上运行以获取:

[6 9 9 9 9 2 0 7 4 8]

现在在大小为 8 的子数组上,例如 A[1…8],得到:

[9 9 9 9 9 9 9 9 4 8]

现在加倍会得到 16 个 9,但我们只有 10 个位置,所以轮到 A[1…10],得到:

[9 9 9 9 9 9 9 9 9 9]

更新 3:由于这仅在最坏的情况下是最佳的,因此它实际上不是原始问题的答案,它要求找到一种方法来找到所有输入的最小操作数。我将关于暴力破解的句子误解为关于使用中值操作的暴力破解,而不是寻找最小操作序列。

【讨论】:

  • 对于 N=30,如果数组中的每个元素都具有相同的值,则答案将为 0。因此答案取决于给定的数组
  • 是的,但是,正如我所写的,在最坏的情况下加倍是最佳的。
  • @njlarsson:: 能否请您说的具体一点。无法理解。伪代码将不胜感激
  • @njlarsson :您的方法没有产生最少的转换步骤。
  • 没有?我认为在最坏的情况下确实如此(在原始状态下仅出现一次最大值)。我很想知道你如何改进它。
【解决方案2】:

这是codechef Long Contest的问题。由于比赛已经结束,所以很尴尬,我正在粘贴问题设置器的方法(来源:CC Contest编辑页)。

“数组的任何状态都可以表示为二进制掩码,每位 1 表示对应的数等于最大值,否则为 0。您可以使用状态 R[掩码] 和 O(n) 转换运行 DP。您可以证明(或只是相信)statest 的数量不会很大,当然如果您运行良好的 DP。我们的 DP 的状态将是等于 max 的数字的掩码。当然,它是有意义的仅对这样的子数组 [l; r] 使用操作,即 1 位的数量至少与子掩码 [l; r] 中的 0 位的数量一样多,因为否则什么都不会改变。另外你应该注意到,如果左边你的操作的界限是 l 最好只用最大可能的 r 进行操作(这给出了等于 O(n) 的转换数)。对于 C++ 编码器来说,使用映射结构来表示所有状态也很有用。"

C/C++ 代码是::

#include <cstdio>
#include <iostream>
using namespace std;

int bc[1<<15];
const int M = (1<<15) - 1;

void setMin(int& ret, int c)
{
    if(c < ret) ret = c;
}

void doit(int n, int mask, int currentSteps, int& currentBest)
{
    int numMax = bc[mask>>15] + bc[mask&M];
    if(numMax == n) {
        setMin(currentBest, currentSteps);
        return;
    }
    if(currentSteps + 1 >= currentBest)
        return;
    if(currentSteps + 2 >= currentBest)
    {
        if(numMax * 2 >= n) {
            setMin(currentBest, 1 + currentSteps);
        }
        return;    
    }  

    if(numMax < (1<<currentSteps)) return;

    for(int i=0;i<n;i++) 
    {
        int a = 0, b = 0;
        int c = mask;
        for(int j=i;j<n;j++)
        {
            c |= (1<<j);
            if(mask&(1<<j)) b++;
            else a++;
            if(b >= a) {
                doit(n, c, currentSteps + 1, currentBest);
            }
        }
    }
}

int v[32];
void solveCase() {
    int n;
    scanf(" %d", &n);
    int maxElement = 0;
    for(int i=0;i<n;i++) {
        scanf(" %d", v+i);
        if(v[i] > maxElement) maxElement = v[i];
    }
    int mask = 0;
    for(int i=0;i<n;i++) if(v[i] == maxElement) mask |= (1<<i);
    int ret = 0, p = 1;
    while(p < n) {
        ret++;
        p *= 2;
    }
    doit(n, mask, 0, ret);
    printf("%d\n",ret);
}

main() {
    for(int i=0;i<(1<<15);i++) {
        bc[i] = bc[i>>1] + (i&1);
    }
    int cases;
    scanf(" %d",&cases);
    while(cases--) solveCase();
}

【讨论】:

【解决方案3】:

问题制定者方法具有指数级复杂性。 N = 30 非常好。但对于更大的尺寸,情况并非如此。我认为,找到指数时间解决方案更有趣。我找到了一个,复杂度为 O(N4)。

这种方法利用了这样一个事实,即最优解从一组连续的最大元素开始,并且只扩展这一个组,直到整个数组被最大值填充。

为了证明这个事实,取 2 个连续最大元素的起始组,并以最佳方式扩展它们中的每一个,直到它们合并为一个组。假设第 1 组需要 X 轮才能增长到 M 大小,第 2 组需要 Y 轮才能增长到相同大小 M,并且在轮 X + Y + 1 时这些组合并。结果是一组大小至少为 M * 4。现在不是为第 2 组转 Y,而是为第 1 组额外转 X + 1。在这种情况下,组大小至少为 M * 2,最多为 M / 2 (即使我们最初计算最大元素,也可能包含在步骤 Y 中)。在此更改之后,在第 X + Y + 1 轮,合并组大小至少为 M * 4,仅作为第一个组扩展的结果,从第二组中添加至少一个元素。因此,在这里扩展单个组会以相同的步数产生更大的组(如果 Y > 1,它甚至需要更少的步数)。由于这适用于相等的组大小 (M),因此对于不相等的组会更好。这个证明可以扩展到多组(多于两个)的情况。

要处理单组连续最大元素,我们只需要跟踪两个值:组的开始位置和结束位置。这意味着可以使用三角矩阵来存储所有可能的组,从而允许使用动态规划算法。

伪代码:

For each group of consecutive maximal elements in original array:
  Mark corresponding element in the matrix and clear other elements
  For each matrix diagonal, starting with one, containing this element:
    For each marked element in this diagonal:
      Retrieve current number of turns from this matrix element
      (use indexes of this matrix element to initialize p1 and p2)
      p2 = end of the group
      p1 = start of the group
      Decrease p1 while it is possible to keep median at maximum value
      (now all values between p1 and p2 are assumed as maximal)
      While p2 < N:
        Check if number of maximal elements in the array is >= N/2
          If this is true, compare current number of turns with the best result \
               and update it if necessary
          (additional matrix with number of maximal values between each pair of
            points may be used to count elements to the left of p1 and to the
            right of p2)
        Look at position [p1, p2] in the matrix. Mark it and if it contains \
            larger number of turns, update it
        Repeat:
          Increase p1 while it points to maximal value
          Increment p1 (to skip one non-maximum value)
          Increase p2 while it is possible to keep median at maximum value
        while median is not at maximum value

为了保持算法简单,我没有提到组从位置 0 开始或在位置 N 结束的特殊情况,跳过初始化并且没有进行任何优化。

【讨论】:

    猜你喜欢
    • 2014-09-23
    • 2014-08-06
    • 2012-06-06
    • 2016-09-10
    • 1970-01-01
    • 2013-02-02
    • 2017-04-24
    • 2013-09-02
    • 2011-10-31
    相关资源
    最近更新 更多