LeftRightDigitsGame2

作者:陈通 陈俊锟

关键词:动态规划 记忆化搜索

试题来源

Topcoder SRM 556 Div1 500

试题大意

你现在有一叠纸牌,共张,用表示,每张纸牌上写有一个数字。你每次取出牌堆顶部的第一张牌,把它放在桌子上。从第二张牌开始,你每次必须把牌放在桌上已有纸牌的最左侧或最右侧。最终这张牌组成了一个位数。现在给定一个位数,希望你求出大于等于的可能结果中最小的一个位数。

算法介绍

算法一

我们可以发现,牌堆顶部的连续张牌,在这个位数中也一定是连续的位。我们对操作逆向考虑。每次对于牌堆底部的每一张牌,考虑放在左侧还是放在右侧。若我们已知前张牌在位数中所对应的区间,当我们决定了第张牌的位置,那么我们就可以确定前张牌的区间了。

定义状态,表示所求n位数中位组成的数与位组成的数的大小关系为,所求n位数中位组成的数与位组成的数的大小关系为,用牌堆中前张牌,放在的位置上所组成最小的位数,满足所组成的位数与的大小关系满足题意。其中分别表示小于、等于和大于关系。所求即为。对于状态,分别枚举第放在位置和位置两种情况,对于每种情况求出新的大小关系可以根据求出。当时,我们很容易判断当前状态是否满足题意,若不满足题意则进行标记,否则该状态为牌堆第一张牌所写数字。

状态总数为。每个状态转移时,字符串比较和赋值的时间复杂度为。总时间复杂度为

算法一的一种更详细的描述

考虑将操作串倒着往前做,我们看到最后一个操作串只可能在最终串的开头或者结尾,删掉以后最终串还是一个连续的区间。可以设计一个状态, 表示操作串倒着做到第 位时,最终串剩下的区间为,一个状态(后面提到)为 时,字典序最小的情况。注意到对于两个状态, 字典序越小越好,如果两个状态的 相等,则 的字典序越小越好。因此我们可以用一个 pair<string,string> 来保存状态。我们设一个状态中,L 为最终序列的前缀(即 ),R 为最终序列的后缀(即 )。

我们考虑有多少种合法的情况:

  1. L 的字典序严格大于相同位置上的 的前缀。那么我们就希望 R 越小越好。

  2. L 的字典序刚好等于相同位置上的 的前缀。这时也有两种情况:

    1. 的区间里面将会有严格大于 的情况,那么这是我们也希望 R 越小越好。

    2. 的区间里面的字典序也与 的大小相同,那么此时我们希望 R 在满足大于等于 对应位置的情况下尽可能的小。

接下来我们规定状态 中的 :1.的情况 ,2.1 的情况 ,2.2 的情况为 。有了这几种情况我们就可以转移:(以下我考虑的是主动转移的情况)

  1. 如果已经有严格大于t的前缀了,那么剩下的序列就可以直接转移,只要使得字典序最小就好了。

  2. 我们再考虑 L 刚好顶 的下界,R 不一定顶 的下界的情况怎么来转移。

    1. 我们先考虑将 这个字符放前面:

      1. 如果 ,那么把这个字符放前面时,原来顶着 的下界的就会变成严格大于的(即变成了状态 0 ),于是:

      1. 如果 ,那么原来顶 的下界的现在还是顶着 的下界:

      1. 如果 ,此时这个字符放前面的情况不成立,不转移。
    2. 我们再来考虑一下 字符放后面:

      1. 如果 ,那么这个字符放后面以后就可以使得原来不是严格大于的R后缀必然大于所构造串的等位后缀。

      1. 对于任意情况,我们都可以将这个字符直接扔在后面来转移给

      1. 最后是L和R都顶 的下界的情况。

      2. 我们先考虑将 这个字符放前面:

        1. 如果 ,那么把这个字符放前面时,原来顶着 的下界的就会变成严格大于的(即变成了状态 ),于是:

        2. 如果 ,那么原来顶 的下界的现在还是顶着 的下界,(R顶着 的下界的情况也属于R不一定顶着 的下界的情况,所以也可以转移给1):

        3. 如果 ,此时这个字符放前面的情况不满足条件,不转移。

      3. 我们再来考虑一下 字符放后面:

        1. 如果 ,那么这个字符放后面R仍然是大于等于t的同位后缀。

        2. 对于任意情况,我们都可以将这个字符直接扔在后面来转移给1的情况,(理由同 3.1.2 的括号内内容)。

最后统计答案时,统计字典序最小的所有 和满足条件的 即可。时间复杂度为

算法二

如果把数字看作字符串,那么这就是一个和字典序有关的问题,我们可以考虑按位确定。

简化问题

首先考虑一个简化的问题,就是给出 ,要求通过在左侧(称为 L 操作)或右侧(称为 R 操作)添加构造一个字典序最小的数(不需要大于等于 )。如果我们按高位到低位枚举了一个前缀 ,满足至少存在一个包含 为前缀的 串 可以通过上述方式构造,那么我们可以从小到大枚举这一位的数字 ,判断是否至少存在一个包含 为前缀的串 可以通过上述方式构造出。一旦找到一个可行的 ,则令 ,并确定下一位。现在,问题就转化为如何判断一个前缀 是否合法。

考虑可构造出的串是由什么构成的:分为 3 段,刚开始的一段全部由 L 操作产生,中间是初始字符 O 即 的第一位,末尾一段全部由 R 操作产生。则我们枚举的一个前缀将会是以下情况:

LLLLLLLLLORRRRRRRRRXXXXXXX

L、R、O 表示这个数字是通过哪一个操作得到的,末尾的 X 表示还没有确定的前缀。注意到最靠近 O 的数字来自于最早的操作,也就是说 L 这一段的最终顺序和这些 L 操作的执行顺序相反。对于我们的判定,如果这个前缀 可以只由 L 和 O 部分构成,则判定成功,这只需要判定 反串是否是 的子序列即可。

如果没有,则我们就要考虑一般情况了。枚举 O 在 中的位置,那么我们可以把 分为 3 段,称它们为 ,则如果我们枚举了 O 的位置 ,那么 的一般情况如下:

123456789OABCDEFGHI

还原回 去看,那么可能会是以下情况:

O123456HI78G9XXXFEXDCXXBXXAX

如果将 中匹配,那么它们一定是最早的若干个 R 操作,而一个数不是 L 操作就是 R 操作,所以直到 在序列中匹配完毕,即

O123456HI78G9

这个位置(设为 ),那么 每一个数的操作都确定了。而 前面的 L 操作影响的是 的后半段,因此我们知道: 匹配完毕之前,所有的位置不是和 匹配,就是和 反串匹配

得到这个性质后,我们就可以匹配了。设布尔数组 表示 中, 已经匹配到了第 位、第 的反串已经匹配到了第 位是否可行。转移只需要枚举 是匹配给哪一个串即可了。

匹配完毕后,考虑后半部分

XXXFEXDCXXBXXAX

如果状态 可行,那么我们只需要判断 中,是否包含 的反串的 作为子序列即可。由于子序列有贪心性质,我们可以直接由 反串倒着从后往前匹配,求出最迟匹配位置即可。

这个算法一次判定的复杂度是 (枚举 O 然后匹配),总复杂度是 ,但复杂度非常不满,可以通过所有数据。

一般问题

此时要考虑必须大于等于 ,如果我们直接按照上述方式按位确定(仅考虑是否顶 的下界),这里可能会错误地得到无解,如 lowerBound=02461plowerBoundO(n^4)O(n^4)$$,然而复杂度非常不满,可以 1MS 内通过 TC 的每个数据。

results matching ""

    No results matching ""