GDOI2016模拟4.22 无界单词 字符串上的动态规划

题目大意

给定一个 N K ,问所有长度为 L 的仅包含字符 ab 的单词中有多少个无界单词,以及字典序第 K 的无界单词是什么?
有界单词:对于一个单词 S ,如果存在一个长度 L ,满足 0<L<length(S) ,并且使得 S 长度为 L 的前缀与S长度为 L 的后缀相同,则称 S 是有界的。
无界单词:对于一个单词 S ,如果不存在一个 L 使之成为有界单词,即它为无界单词。
题目有 T 组测试数据。

L<=64 T<=4

解题思路

相比第二问来说,肯定是第一问比较好处理,那我们先来考虑一下第一问。
我们可以令 Fi 表示长度为 i 的单词有多少个是无序的,计算时无界单词考虑起来比较繁琐,所以可以用所有单词的数量减去有界单词的数量来得到 Fi 的值。那么现在就需要考虑长度为 i 的有界单词的数量。

假如当前的有界单词长度为 L 的前缀和后缀相等,我们对每个单词统计数量是只考虑最小的 L 来保证不会算重。一个显然的性质就是当 L>i2 时,肯定存在一个更小的 L 使之满足要求,所以就只需考虑小于等于 i2 L 。考虑最小的 L 时我们可以直接把之前统计出的长度小于 i2 的无界单词复制一遍放在头和尾,中间剩下字符任选,这样就可以推出 Fi 。总的来说就可以写出递推式 Fi=2ii2j=1Fj2i2j FN 就是第一问的答案,并且与第二问也有关联。

对于第二问,容易想到的是我们可以一位一位的判断能不能放 a ,现在的问题就变成了如何统计一个确定前 Len 个字符的字符串能构成无界单词的个数。其实跟第一问差不多,也要维护一个 Fi 表示前 i 个字符组成的字符串(包括未确定的)组成的无序单词的数量。但是递推时需要分几种情况讨论一下。
假设当前已经确定了前 Len 个字符,现在递推到第 i 位,枚举到长度为 j 的无序前缀(这里的 i j 和上面的含义一样)

  1. iLen
    那么当前要求的前 Len 个字符都是已经确定。所以直接用 KMP 判断一下已经确定的前 i 个字符组成的单词是否是无序单词就可以了。

  2. Lenj
    那么对当前的 Fi 造成影响的 Fj 都在前面的转移中特殊考虑过了,所以第一问一样直接转移就可以了。

  3. Len>j Lenij
    这种情况跟第二种差不多,但是相同的前后缀中有些任选的位置已经在被确定了,所以只需把中间任选的方案( 2i2j ),改成除了确定的位置任选( 2i2j(Lenj)

  4. Len>ij
    当出现这种情况时不仅中间任选字符已经确定,而且我们把前缀复制到后缀时,放后缀的有些位置的字符也已经确定。所以我们只需用 Hash 判断一下当前超出的位置是不是当前单词的前缀。而由于可以任选字符的位置已经确定,所以如果匹配成功,只需把有序单词的个数加 +E

程序

//YxuanwKeith
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;
typedef unsigned long long LL;

const int MAXN = 65, HNum = 15731;

LL K, Pow[MAXN], F[MAXN], Has[MAXN], H[MAXN];
int Len, Last, Next[MAXN], N, Pj, Ans[MAXN];

void Prepare() {
    Pow[0] = Has[0] = 1;
    for (int i = 1; i <= 64; i ++) Pow[i] = Pow[i - 1] * 2;
    for (int i = 1; i <= 64; i ++) Has[i] = Has[i - 1] * HNum;
}

bool Judge(int Len, int r) {
    int l = r - Len + 1;
    return H[Len] - H[0] * Has[Len] == H[r] - H[l - 1] * Has[r - l + 1]; 
}

LL Solve() {
    memset(F, 0, sizeof F);
    for (int i = 1; i <= N; i ++) {
        if (i < Len && !Next[i]) F[i] = 1;
        if (i < Len) continue;
        F[i] = Pow[i - Len];
        for (int j = 1; j <= i / 2; j ++) {
            LL Cnt;
            if (Len <= j) Cnt = Pow[i - 2 * j];
            if (Len > j && Len <= i - j) Cnt = Pow[i - 2 * j - (Len - j)];
            if (Len > i - j) Cnt = Judge(Len - (i - j), Len);
            F[i] -= F[j] * Cnt;
        }
    }
    return F[N];
}

void PushIn(int c) {
    Ans[++ Len] = c, H[Len] = H[Len - 1] * HNum + c;
    if (Len == 1) return;
    for (; Pj && Ans[Pj + 1] != Ans[Len]; Pj = Next[Pj]);
    if (Ans[Pj + 1] == Ans[Len]) Pj ++;
    Next[Len] = Pj;
}

void Work() {
    Len = 0, Pj = 0;
    scanf("%d%lld", &N, &K);
    printf("%lld\n", Solve());
    for (int i = 1; i <= N; i ++) {
        Last = Pj;
        PushIn(0);
        LL Num = Solve();
        if (Num < K) {
            Pj = Last, Len --;
            K -= Num;
            PushIn(1);
        }
    }
    for (int i = 1; i <= N; i ++) printf("%c", Ans[i] + 'a');
    printf("\n");
}

int main() {
    Prepare();
    int Test;
    scanf("%d", &Test);
    for (; Test; Test --) Work();
}

你可能感兴趣的:(GDOI2016模拟4.22 无界单词 字符串上的动态规划)