Algorithm Ctrick位无法理解

Algorithm Ctrick位无法理解,algorithm,tree,segment-tree,Algorithm,Tree,Segment Tree,这是spoj的一个问题 它说 魔术师洗牌一小包牌,将其正面朝下,然后执行以下步骤: 顶部的卡被移动到包的底部。新的顶牌面朝上发到桌上。这是黑桃的王牌 两张牌一次从上到下移动一张。下一张牌面朝上发到桌上。这是两个黑桃 三张牌一次移动一张 这种情况一直持续到第n张牌和最后一张牌变成黑桃的第n张 如果魔术师知道如何事先安排牌(并且知道如何进行假洗牌),这个令人印象深刻的把戏就会奏效。您的程序必须确定给定数量的卡的初始顺序,1≤ N≤ 20000. 输入 输入的第一行是一个正整数,指示要遵循的测试用例

这是spoj的一个问题 它说

魔术师洗牌一小包牌,将其正面朝下,然后执行以下步骤:

  • 顶部的卡被移动到包的底部。新的顶牌面朝上发到桌上。这是黑桃的王牌

  • 两张牌一次从上到下移动一张。下一张牌面朝上发到桌上。这是两个黑桃

  • 三张牌一次移动一张

  • 这种情况一直持续到第n张牌和最后一张牌变成黑桃的第n张

  • 如果魔术师知道如何事先安排牌(并且知道如何进行假洗牌),这个令人印象深刻的把戏就会奏效。您的程序必须确定给定数量的卡的初始顺序,1≤ N≤ 20000.

    输入

    输入的第一行是一个正整数,指示要遵循的测试用例数。每个案例由一行组成,其中包含整数n。 输出

    对于每个测试用例,输出一行值1到n的正确排列,空格分隔。显示包装顶部卡片的第一个数字,等等… 范例

    输入: 二,

    四,

    五,

    输出:

    2 1 4 3

    31452

    现在我能想到的唯一解决方案是使用队列并模拟流程。 但那将是O(n^2)。我读了评论,他们建议使用位的段树。 我知道段树和位,但在这个问题上无法理解如何实现它们。请给出一些方法。
    Thanx

    我不知道为什么这个问题应该与位树或段树联系起来,但我使用简单的“O(N^2)”模拟解决了这个问题

    首先,此问题的时间限制为
    11s
    ,并且
    N==20000
    。这表明
    O(kN)
    解决方案可能通过问题。我相信你认为这个
    k
    应该是
    N
    ,因为简单的模拟需要这个,但不知何故它可以优化

    让我们看看当N==5时如何构造序列:

    Round 1, count 1 space starting from first space after last position:        _ 1 _ _ _
    Round 2, count 2 spaces starting from first space after last position:       _ 1 _ _ 2
    Round 3, count 3 spaces starting from first space after last position:       3 1 _ _ 2
    Round 4, count 4 spaces starting from first space after last position:       3 1 4 _ 2
    Round 5, count 5 spaces starting from first space after last position:       3 1 4 5 2
    
    我们可以看到一个很好的模式:对于圆形
    i
    ,我们应该从最后一个位置后的第一个空间开始计算
    i
    空间,并在必要时向后弯曲

    然而,关键的一步是:经过几轮之后,剩下的空间将小于要计算的空间。在这种情况下,我们可以使用
    mod
    来节省时间

    例如,在上一个示例的第4轮中,我们只剩下2个空格,但要计算4个空格。如果我们数到4,那是浪费时间计算4个步骤相当于从最后一个位置后的第一个空格开始计算4%2==0个空格。您可以自己验证这一点:)

    因此,我们可以使用以下代码模拟此过程:

    memset(ans, 255, sizeof(ans));
    while (cur <= n)
    {
        int i, cnt;
        int left = n - cur + 1; // count how many spaces left
        left = cur % left + 1; // this line is critical, mod to save time!
    
        for (i = pos, cnt = 0; ; ++i) // simulate the process
        {
            if (i > n) i = 1;
            if (ans[i] == -1) ++cnt;
            if (cnt == left) break;
        }
    
        ans[i] = cur;
        pos = i;
    
        ++cur;
    }
    
    memset(ans,255,sizeof(ans));
    而(curn)i=1;
    如果(ans[i]=-1)++cnt;
    如果(cnt==左)断开;
    }
    ans[i]=cur;
    pos=i;
    ++cur;
    }
    
    如果您想使用Fenwick树(BIT)解决此问题,请仔细查看nevets发布的解决方案,尤其是这一部分(感谢nevets的绘图):

    使用上述方法寻找正确的自由空间的时间复杂度为O(N),因为我们必须遍历所有空间(总复杂度为O(N^2))。请注意,我们可以使用以下公式计算下一个位置:

    free(next_pos) = (free(current_pos) + next_number) mod free(total) + 1
    
    其中free(x)告诉我们一个位置上有多少个可用空间。这不是next_pos的直接公式,但它告诉我们需要满足什么,所以我们可以使用此信息对其进行二进制搜索

    剩下要做的唯一一件事就是进行自由空间计算,这就是BIT发挥作用的地方,因为它为查询和更新提供了O(logn)的时间复杂度。现在,查找可用空间的时间复杂度为O(log^2 N),总时间复杂度为O(N log^2 N)

    至于行驶速度:

    • 3.16s用于建议的进近
    • 1.18s使用队列旋转元素
    • 使用链表旋转0.60秒
    • 0.02s使用一个位
    我必须说我对速度的提高感到非常惊讶:-)


    注意:如果您不确定如何使用该位,请通过将所有值更新为+1来初始化。将插槽标记为已占用时,只需将其更新为-1即可。

    您尝试过该问题吗?你现在的判决是什么?
    free(next_pos) = (free(current_pos) + next_number) mod free(total) + 1