【题目描述】
如果一个字符串包含两个相邻的重复子串,则称它是“容易的串”,其他串称为“困难的串”。例如, BB、ABCDABCD都是容易的串,而D、DC、ABDAD、CBABCBA都是困难的串。
输入正整数n和L,输出由前L个字符组成的、字典序第k个困难的串。例如,当L=3时,前7个困难的串 分别为A、AB、ABA、ABAC、ABACA、ABACAB、ABACABA。输入保证答案不超过80个字符。
样例输入:
7 3
30 3
样例输出:
ABACABA
ABACABCACBABCABACABCACBACABA
【整体思路】
想要求第n小的困难的串,我首先想到的方法是生成-测试法,也就是依次生成并判断生成的是否是“困难的串”:这需要依次检查所有长度为偶数的子串,并判断前一半是否等于后一半。
这样做的后果是会产生很多不必要的计算,比如下面的串:
ABCABC
经判断这个串是“简单的串”,可是当枚举到 ABCABCA 时,仍需要进行 ABCABC 这个子串的判定–这显然造成了重复的计算。理论上来讲,当判定了上面的例子为简单串后,就没有必要再去以它为起点生成更多的串了,因为这些生成的串包含前面的串,故一定也是简单的。
回溯法的一个优点就是可以剪掉一些不必要的计算,因为当不满足条件时,本层的递归便不再进行,也就省去了一些不必要的计算。
其实回想前面学到的八皇后问题,它们的判断思想是一脉相承的。对八皇后问题来说,使用二维数组 vis[3][] 来保存上一层放置的状态,我们不用判断所有前面(n-1)层的,因为不符合要求的放置没有后续的递归操作,只有符合要求的部分才会执行DFS(cur+1)。
【判断部分】
因为不需要考虑所有的子串(不合格的已经被筛掉了),因此只需要考虑不同长度的后缀串是否相同。判断原理如下图:
先判断后缀长度为1的:
再判断后缀长度为2的:
以此类推,直到 j*2>=len+1 为止。
不难发现,比较的位置是从每次从尾端开始,因为比较的距离为i,所以作比较的元素应该是a[cur]和a[cur-i],又因为需要从a[cur]比较到a[cur-i],因此应该加上一层循环来控制移动的次数。
【代码】
//困难的串
#include<iostream>
#include<string>
#include<cmath>
#include<cstring>
#include<ctype.h>
using namespace std;
const int maxn = 10000 + 10;
int cnt = 0, n, L, a[maxn];
bool check(int cur, int j) //j为枚举的倍数
{
for (int i = 0;i < j;i++)
if (a[cur - i] != a[cur - j - i]) return true;
return false;
}
int DFS(int cur) //cur个元素
{
if (cnt++ == n)
{
for (int i = 0;i < cur;i++) cout << (char)(a[i] + 'A');cout << endl;
return 0;
}
for (int i = 0;i < L;i++)
{
int ok = 1;
a[cur] = i;
for (int j = 1;j * 2 <= cur + 1;j++) //j为枚举的倍数
if (!check(cur, j)) //只要前后完全相同就非法
{
ok = 0;break;
}
if (ok) //如果不非法就继续递归
if (!DFS(cur + 1)) return 0;
}
return 1;
}
int main()
{
cin >> n >> L;
memset(a, 0, sizeof(a));
DFS(0);
return 0;
}