例题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;
}
<think>好的,用户想了解动态规划算法例题。我需要先回顾动态规划的基本概念,然后找一个合适的例子来讲解。动态规划通常用于优化问题,比如最短路、最长子序列等。用户可能对递归和记忆化不太熟悉,所以得选个简单易懂的例子。 首先,斐波那契数列是个经典例子,但可能太简单了。或者选背包问题,不过0-1背包对新手来说可能有点难。也许最长公共子序列(LCS)比较合适?或者找零钱问题?选哪个呢? 考虑用户的需求,他们可能希望看到步骤分解,如何将问题分解为子问题,构建状态转移方程,以及填表过程。硬币找零问题比较直观,容易理解状态转移。比如,用最少的硬币组成某个金额。 确定例题后,我需要按步骤解释:问题描述、状态定义、转移方程、初始条件、填表过程、结果获取。还要注意边界情况,比如金额为0时需要0个硬币,或者无法找零的情况。 另外,用户可能想知道动态规划的优势,比如避免重复计算,时间复杂度优化。需要比较递归和动态规划的区别,说明为什么DP更高效。 还要检查是否遵循了系统级指令:数学公式用$...$,独立公式用$$...$$,结构清晰,用中文。确保LaTeX语法正确,比如下标、上标是否正确,方程对齐是否合适。 可能遇到的问题:用户对状态转移方程的理解困难,需要详细解释每个符号的含义。或者对填表过程不清楚,可以画个表格示例,但文本中只能用文字描述。 最后,总结动态规划的适用场景,以及解决这个例题的关键步骤,帮助用户举一反三。确认回答是否覆盖了问题分析、解决步骤和优化原理,确保用户能跟着一步步理解。</think>### 动态规划算法例题详解 动态规划(Dynamic Programming, DP)常用于解决具有**重叠子问题**和**最优子结构**的问题。下面通过一个经典例题**“零钱兑换”**来逐步解析动态规划的核心思想与应用步骤。 --- #### **例题描述** 给定不同面额的硬币数组 $coins$ 和一个总金额 $amount$,求凑出 $amount$ 所需的最少硬币个数。若无法凑出,返回 $-1$。 **示例**: - 输入:$coins = [1, 2, 5]$, $amount = 11$ - 输出:$3$(解释:$5 + 5 + 1 = 11$) --- #### **动态规划解题步骤** 1. **定义状态** 设 $dp[i]$ 表示凑出金额 $i$ 所需的最少硬币个数。 **目标**:求 $dp[amount]$。 2. **状态转移方程** 对于每个金额 $i$,遍历所有硬币 $coin$: - 若 $coin \leq i$,则 $dp[i] = \min(dp[i], dp[i - coin] + 1)$ **解释**:选择当前硬币 $coin$ 后,剩余金额为 $i - coin$,其最优解为 $dp[i - coin] + 1$(加1是因为使用了当前硬币)。 3. **初始化** - $dp[0] = 0$(金额0不需要硬币) - 其他 $dp[i]$ 初始化为 $\infty$(表示暂未找到解) 4. **填表顺序** 从小到大遍历金额 $i$,逐步更新 $dp$ 数组。 --- #### **代码实现(Python)** ```python def coinChange(coins, amount): dp = [float('inf')] * (amount + 1) dp[0] = 0 for i in range(1, amount + 1): for coin in coins: if coin <= i: dp[i] = min(dp[i], dp[i - coin] + 1) return dp[amount] if dp[amount] != float('inf') else -1 ``` --- #### **复杂度分析** - 时间复杂度:$O(n \times k)$,其中 $n$ 为金额,$k$ 为硬币种类数。 - 空间复杂度:$O(n)$,用于存储 $dp$ 数组。 --- #### **关键点总结** 1. **重叠子问题**:多次计算相同子问题(例如 $dp[5]$ 可能被多次调用),动态规划通过存储中间结果避免重复计算。 2. **最优子结构**:大问题的最优解依赖于子问题的最优解(例如 $dp[11]$ 依赖 $dp[10]$ 或 $dp[9]$)。 3. **边界条件处理**:初始化 $dp[0] = 0$ 是递推的基础。 --- #### **其他经典动态规划问题** 1. **最长递增子序列**(LIS):求序列中最长的严格递增子序列长度。 2. **背包问题**:在容量限制下选择物品最大化价值。 3. **编辑距离**:计算将一个字符转换为另一个字符的最小操作次数。 动态规划的核心在于**将问题分解为子问题,并利用历史状态推导当前状态**。通过练习经典例题,可以逐步掌握其设计技巧。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值