【Kickstart】2018 Round F - Palindromic Sequence

解法

这个题我看解答都想了好久= =

对于小数据集,当N<=100时,可以通过计算出以S为前缀的字符串有多少个来慢慢遍历,记作 P N ( S ) P_N(S) PN(S)
为了方便大数据集的分析,我们把空串也算上,空串是K=0对应的串

处理过程伪代码如下:

def solve(l,N,K):
  prefix = ""
  while True:
    if K < P(prefix+"$"):   # 是否可以直接成回文串,如果可以P是1,不可以P是0
      return len(prefix)
    K -= P(prefix+"$")
    for i in range(l):
      c = 'a'+i
      if K<P(prefix+c):
        prefix += c
        break
      else:
        prefix -= P(prefix+c)
    else: # 如果所有可用字符都遍历完了也不确定下一个字符是啥,那就说明K太大了
      return 0

P N ( S ) P_N(S) PN(S)的求法如下:
P N ( S ) = ∑ l = l e n ( S ) N P ( S , L ) P_N(S) = \sum_{l = len(S)}^NP(S,L) PN(S)=l=len(S)NP(S,L),其中, P ( S , L ) P(S,L) P(S,L)表示以S开头的,长度为L的回文串

P(S,L)的求法如下:

  • 如果S的长度超过L的一半,那么要检查超过部分是否是回文,如果是,P(S,L)=1,否则为0
  • 如果S的长度不足L的一半, l e n ( S ) &lt; ⌊ L + 1 2 ⌋ len(S)&lt;\lfloor\frac{L+1}{2}\rfloor len(S)<2L+1,不足一半的那部分可以随便变换,所以可能性有 l ⌊ L + 1 2 ⌋ − l e n ( S ) l^{\lfloor\frac{L+1}{2}\rfloor-len(S)} l2L+1len(S)

现在如果N可能很大,我们不能一个一个字符推测了,而是应该根据K的大小来直接得到一个前缀:

  • 如果K<=N,首先我们知道,这个回文串序列的前N个一定是长度分别为1,2,...,N的全是a的串,所以此时第K个串对应的长度一定是K

  • 如果K>N
    首先,以ta为前缀的回文串,我们记作 A t A_t At,并且有 P N ( A t ) = ∑ L = t 2 t − 1 P ( A t , L ) + ∑ L = 2 t N P ( A t , L ) P_N(A_t) = \sum_{L=t}^{2t-1}P(A_t,L)+\sum_{L=2t}^{N}P(A_t,L) PN(At)=L=t2t1P(At,L)+L=2tNP(At,L),前一部分很明显满足条件的只能是全a的串,所以等于t,并且它们刚好是以 A t A_t At开头的前t个回文串;而后一部分全部都是长度为L A t A_t At开头和结尾的回文串,它等价于长度为L-2t的任意回文串,因此最后我们有: P N ( A t ) = t + ∑ L = 2 t N P ( &quot; &quot; , L − 2 t ) = t + ∑ L = 0 N − 2 t P ( &quot; &quot; , L ) = t + P N − 2 t ( &quot; &quot; ) P_N(A_t) = t+\sum_{L=2t}^{N}P(&quot;&quot;,L-2t)\\=t+\sum_{L=0}^{N-2t}P(&quot;&quot;,L)=t+P_{N-2t}(&quot;&quot;) PN(At)=t+L=2tNP("",L2t)=t+L=0N2tP("",L)=t+PN2t("")

    P N − 2 t ( &quot; &quot; ) P_{N-2t}(&quot;&quot;) PN2t("")恰好对应于一个更小规模( N ′ = N − 2 t N&#x27;=N-2t N=N2t)的问题,也就是说,如果我们能确定K对应的回文串开头有多少个a,那么问题的规模就能减小

    现在,按照我们的遍历逻辑:
    1. 前缀为"a"时,如果K==1,那么对应的串是"a"(当然这在K>N时不会发生)
    如果K>=2K-2<P("aa"),那么K对应的串将以"aa"开头,而由于P("aa")很大,这是很有可能的
    而如果K>=2 K − 2 &lt; P N − 2 ( &quot; &quot; ) K-2&lt;P_{N-2}(&quot;&quot;) K2<PN2(""),那么K对应的回文串就落在了P("a")第二部分,它将是以"a"开头,"a"结尾的串,并且是长为N-2的字典序回文串里的第K-2个。
    2. 接下来,可能由于K>=3并且K-3<P("aaa"),所以对应的串将以"aaa"开头,由于K>=4 K − 4 &lt; P N − 4 ( &quot; &quot; ) K-4&lt;P_{N-4}(&quot;&quot;) K4<PN4(""),它将是以"aa"开头,"aa"结尾的串,并且是长为N-4的字典序回文串里的第K-4个。

    以此类推,我们有:

    假如K>=t(这条由于K>N一定成立)且 K − t &lt; P ( A t ) K-t&lt; P(A_t) Kt<P(At),那么对应的串将由 A t A_t At开头
    假如K>=2t K − 2 t &lt; P N − 2 t ( &quot; &quot; ) K-2t&lt; P_{N-2t}(&quot;&quot;) K2t<PN2t(""),那么对应的串将由 A t A_t At开头, A t A_t At结尾,并且是长为N-2t的字典序回文串里的第K-2t个。

    现在我们想确定一个尽量大的t,使得K对应的串以 A t A_t At开头, A t A_t At结尾,并且是是长为N-2t的字典序回文串里的第K-2t个.

    根据 P N − 2 t ( &quot; &quot; ) P_{N-2t}(&quot;&quot;) PN2t("")的定义我们知道 P N − 2 t ( &quot; &quot; ) ≥ P ( &quot; &quot; , N − 2 t ) = l ⌊ N + 1 2 ⌋ − t P_{N-2t}(&quot;&quot;)\ge P(&quot;&quot;,N-2t)=l^{\lfloor\frac{N+1}{2}\rfloor-t} PN2t("")P("",N2t)=l2N+1t,当且仅当2t==N取等号
    K − 2 t ≤ K K-2t\le K K2tK,当且仅当t==0时取等号
    由于N>0,所以它们不会同时取等号,这意味着如果我们让 K ≤ l ⌊ N + 1 2 ⌋ − t K\le l^{\lfloor\frac{N+1}{2}\rfloor-t} Kl2N+1t,就能让 K − 2 t &lt; P N − 2 t ( &quot; &quot; ) K-2t&lt;P_{N-2t}(&quot;&quot;) K2t<PN2t("")
    解得 t = ⌊ ⌊ N + 1 2 ⌋ − log ⁡ l K ⌋ t=\lfloor\lfloor\frac{N+1}{2}\rfloor-\log_lK\rfloor t=2N+1loglK
    如果t<0,说明K太大了,应该返回0。
    然后问题就从solve(l,n,k)变成2t+solve(l,n-2t,k-2t)啦,由于 k ∈ ( 2 32 , 2 64 ) k\in(2^{32},2^{64}) k(232,264),所以 log ⁡ l K \log_lK loglK不会超过64,而n-2t接近 2 log ⁡ l K 2\log_lK 2loglK,所以问题规模不会比小数据集大很多。

# -*- coding:utf-8 -*-

import math
INF = 10**5
BASE = ord('a')
END = '$'

def isPalindrome(s,l,r):
    while l<r:
        if s[l]!=s[r]:
            return False
        l += 1
        r -= 1
    return True

def solve(l,n,k):
    def P(s,l,n):
        d = len(s)
        if s[-1]=='$':
            return 1 if isPalindrome(s,0,d-2) else 0
        res = 0
        for L in range(d,n+1):
            diff = (L+1)//2-d
            if diff>=0 or isPalindrome(s,L-d,d-1):
                res += l**max(0,diff)
        return res

    if k<=n:
        return k
    if l==1:
        return 0
    t = int((n+1)//2-math.log(k,l))
    if t<0:
        return 0
    n -= (t<<1)
    k -= (t<<1)
    prefix = ""
    while True:
        res = P(prefix+'$',l,n)
        if res<=k:
            k -= res
        else:
            return len(prefix)+(t<<1)
        for i in range(l):
            c = chr(BASE + i)
            res = P(prefix+c,l,n)
            if res<=k:
                k -= res
            else:
                prefix += c
                break
        else:
            return 0

if __name__ == "__main__" :
    t = int(input())
    for round in range(1,t+1):
        l,n,k = map(int,input().split())
        print("Case #%d: %d"%(round,solve(l,n,k)))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值