例题7-5 困难的串

【题目描述】
如果一个字符串包含两个相邻的重复子串,则称它是“容易的串”,其他串称为“困难的串”。例如, 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;
}

猜你喜欢

转载自blog.csdn.net/cprimesplus/article/details/84567833
7-5