1 条题解

  • 0
    @ 2025-12-4 18:39:43

    1. 题目推断

    从代码中可以看到:

    • 输入 n,然后有 2*n 个数 a[1..2n]
    • 每个数 a[i]1..n 范围内(因为 lst[a[i]] 数组大小为 n)。
    • 每个数恰好出现两次(因为 lst[a[i]] 非零时表示之前出现过一次)。

    这是一个经典的 括号匹配消除游戏 问题。

    题目描述推测(根据代码逻辑还原):

    有 2n 个数,1 到 n 每个数恰好出现两次。
    每次可以消除两个相同的数,但这两个数之间的所有数必须已经被消除。
    换句话说,消除是一个合法的括号匹配过程:把第一次出现看作左括号,第二次出现看作右括号,只有当中间的括号全部匹配完毕,才能匹配这对括号。
    问:按照某种顺序消除所有数,输出每次消除时,这对数之间还剩多少个未被消除的数(即这对括号内部的、还未匹配的括号数量)。


    2. 代码逻辑分析

    变量解释

    • lst[x]:记录数 x 第一次出现的位置。
    • bit[]:树状数组,记录到当前位置为止已经“删除”了多少个数(这里删除指已经匹配过)。
    • s[]:未使用(可能原代码删除了部分)。
    • ans[]:存储每次匹配时,这对数之间还剩下的未匹配数的数量。
    • top:未使用(可能原代码删除了栈部分)。
    • t:已匹配的对数。

    流程

    for i = 1 to 2n:
        if lst[a[i]] == 0:
            记录第一次出现 lst[a[i]] = i
        else:
            // 第二次出现,进行匹配
            real_i = i - ask(i);                // 在删除一些数后,当前数在剩余序列中的新位置
            real_p = lst[a[i]] - ask(lst[a[i]]);// 第一次出现的位置在剩余序列中的新位置
            v = real_i - real_p - 1;            // 它们之间还剩下多少个数未被匹配
            
            for j = 1 to v:
                ans[++tot] = real_p + j - 1;    // 记录这些“中间剩余数”的编号(从0开始计数)
            
            // 将这对数标记为已删除
            add(lst[a[i]] + 1, 1);
            add(i, 1);
            t++;
    

    3. 详细例子推导

    假设输入:

    n = 3
    a = 1 2 1 3 2 3
    

    每个数出现两次。

    初始:lst 全 0,bit 全 0。

    i=1: a=1

    第一次出现,lst[1]=1

    i=2: a=2

    第一次出现,lst[2]=2

    i=3: a=1

    第二次出现,匹配 lst[1]=1

    • real_i = 3 - ask(3) = 3 - 0 = 3
    • real_p = 1 - ask(1) = 1 - 0 = 1
    • v = 3 - 1 - 1 = 1
    • 循环 j=1 to 1ans[1]=1+1-1=1(表示这对 1 之间还剩下 1 个数未被匹配)
    • 删除这对:add(2,1)add(3,1)
      树状数组表示位置 2 和 3 各有一个删除标记(前缀和表示到此位置已删的数量)。

    i=4: a=3

    第一次出现,lst[3]=4

    i=5: a=2

    第二次出现,匹配 lst[2]=2

    • real_i = 5 - ask(5) 现在 ask(5) = 已删除标记数(位置2和3)在 1..5 中的数量 = 2。 所以 real_i = 5-2=3
    • real_p = 2 - ask(2) ask(2) = 位置2有删除标记吗?位置2的删除标记是从 add(2,1) 来的,ask(2) 得到 1。 所以 real_p = 2-1=1
    • v = 3 - 1 - 1 = 1
    • 循环:ans[2]=1+1-1=1
    • 删除这对:add(3,1)add(5,1) 现在树状数组:位置2=1,位置3=2,位置5=1。

    i=6: a=3

    第二次出现,匹配 lst[3]=4

    • real_i = 6 - ask(6) ask(6) = 删除位置2,3,5 的数量 = 1+2+1 前缀和?我们重新算: 位置2:1,位置3:1(来自第一次删除)+1(来自第二次删除)=2,位置5:1。 ask(6) = bit[4] + bit[6] ?
      更准确:ask(6) = 已删除的数量 = 第一次删除两个数(位置2,3) + 第二次删除两个数(位置3,5)?不对,第二次删除是 add(3,1)add(5,1),所以位置3加了两次(总共删了3个数?) 我们直接模拟树状数组: 初始全0。 第一次删除:add(2,1),add(3,1) 第二次删除:add(3,1),add(5,1) 所以: 位置1:0 位置2:1 位置3:2 位置4:0 位置5:1 位置6:0 ask(6) = sum[1..6] = 0+1+2+0+1+0? 不对,树状数组前缀和是 bit 数组的某个组合。 实际计算: ask(6): 6(110): bit[6]=bit[4]+bit[6]? 我们直接手算: 树状数组维护的是单点更新,前缀和查询。 更新: add(2,1): bit[2]=1, bit[4]=1, bit[8...] add(3,1): bit[3]=1, bit[4]=2, bit[8...] add(3,1): bit[3]=2, bit[4]=3 add(5,1): bit[5]=1, bit[6]=1, bit[8...] 所以: bit[1]=0, bit[2]=1, bit[3]=2, bit[4]=3, bit[5]=1, bit[6]=1 ask(6) = bit[6]+bit[4] = 1+3=4。 所以 real_i = 6-4=2
    • real_p = 4 - ask(4) ask(4) = bit[4]=3,所以 real_p=4-3=1
    • v = 2-1-1=0
    • 循环不执行(因为 v=0)
    • 删除这对:add(5,1)add(6,1)

    最终 ans = [1,1],输出:

    2
    1
    1
    

    4. 答案含义

    ans 中存储的是:每次匹配时,这两个匹配数之间还剩下多少个未被匹配的数(在原序列中的相对位置编号,从0开始)。

    题目要求输出这些剩余数的个数,并按照匹配发生的顺序输出。


    5. 算法总结

    1. 遍历序列,第一次出现的数记录位置。
    2. 第二次出现时,计算在原序列中,这两个位置之间还有多少个数未被匹配(通过树状数组维护已匹配删除的数量,得到“压缩后”的位置差)。
    3. 输出这些中间剩余数的编号(从0开始的索引)。
    4. 更新树状数组标记这对数已匹配。

    6. 时间复杂度

    • 遍历:O(n)
    • 树状数组操作:O(log n)
    • 总复杂度:O(n log n)
    • 1

    「POI2007 R2」立方体大作战 Tetris Attack

    信息

    ID
    5175
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者