题目
- 题目参考网址:https://vjudge.net/problem/UVA-129
- 大意:
如果一个字符串包含两个相邻的重复子串,则称它是“容易的串”,其他串称为“困难的
串”。例如,BB、ABCDACABCAB、ABCDABCD都是容易的串,而D、DC、ABDAB、
CBABCBA都是困难的串。
输入正整数n和L,输出由前L个字符组成的、字典序第k小的困难的串。例如,当L=3
时,前7个困难的串分别为A、AB、ABA、ABAC、ABACA、ABACAB、ABACABA。输入
保证答案不超过80个字符。
知识点
- 回溯法
思路
- 这道题和之前题目不一样的地方是,这道题只要找到答案就会返回,而之前的题目都是遍历寻找所有可行解。
- 难点在于如何判断复杂串,尽可能减少判断的次数,全排列枚举在数量大的时候难以计算
- 聪明的办法是,每次只判断增加后的部分(即添加的最后一个字符) 是否导致当前的串存在容易的串。从头到尾,我们都保证上一次添加到尾部的字符没有导致存在“容易的串”,那么结束,必然该串不是“简单的串”。如何判断?每一次判断尾部起的偶数个串(见注释1),逐个判断左部是否与右部分相同,如存在不同,就不必进行判断(必然不是简单的串)。遍历完仍然都相等,说明存在了“容易的串”。
- 有一个疑问是:30 3的解,为什么长度得到的是28??? ❓
- 其他与常规回溯法思路一致
- 实际题目要求输出更加复杂这里做了简化
- 注释1:
检查 ABCDEFG -> ABCDE (FG) -> ABC (DEFG) -> A (BCDEFG)
代码
# include <iostream>
# include <string>
using namespace std;
int n, l;
bool check(string ans) { // 每次只判断增加后的部分是否导致存在容易的串
int len = ans.length();
for (int i = 1; 2 * i <= len; i++) { //
int start = len - 2 * i, start1 = len - i; // 计算左部分与右部分的起始位置
bool ok = false;
for (int j = 0; j < i; j++) { // 左部分与右部分逐个判断
if (ans[start + j] != ans[start1 + j]) {
ok = true; break;
}
}
if (ok) continue; // ok = true,存在不相等的字符,说明该长度不可能存在“简易的串”,那么检查一个长度(2,4,6,8...2*i)
return false;
}
return true; // 检查完,所有偶数长度都不存在 -> true,是复杂的串
}
bool dfs(string ans = "") {
if (!check(ans)) // 判断
return false; // 不满足条件,返回
if (ans.length() == n) { // 符合条件 & 长度为n
cout << ans << endl; // 实际题目要求输出更加复杂这里做了简化
return true;
}
for (int i = 0; i < l; i++) {
ans += char(65 + i);
// 一个为true,即找到了一个结果,一个返回true,全部返回true,结束
if (dfs(ans)) return true;
ans.pop_back(); // 状态恢复
}
// 特别注意,不然遍历完默认返回了true(但其实并没有找到,应该继续返回上层继续搜索),导致结果异常!
return false;
}
int main() {
// 简单输入
cin >> n >> l;
dfs();
return 0;
}
过程中遇到的问题 & 解决
- dfs()最后一行切记注意返回false,查了半天!!!默认返回为true,但其实并没有找到,应该继续返回上层继续搜索!
测试
输入:
7 3
30 3
结果:✔️
ABACABA
ABACABCACBABCABACABCACBACABACB