TC-SRM617div1-800

本文探讨了在限定步骤内,如何寻找字典序最小的字符串转换方案。通过对经典DP问题的逆向思考,结合枚举和极值策略,提出了一种新颖且高效的解决方案。

这道题的题意就是:已知字符串s1[]最快经过K步的变化可以变化成s2[]问字典序最小的s1[]是多少?另外s1[],s2[]中只会出现英文小写并且长度不超过25并且具有相同的长度。这里的变化包括3中方式:1.添加一个字母2.删除一个字母3.修改一个字母。这道题目一看起来就很容易反应出来它的逆问题是一个非常经典的dp题目。也就是给出s1[],s2[]问需要最少花多少步可以试s1[]变化为s2[]。变化的形式与这道题目中的表述相同。


而对于经典问题的逆问题比较普适的方法是采用枚举答案的方式进行。但是大多数的暴力枚举并不是一个好的解决方式,这道题目应用最暴力的枚举需要的计算次数将远远超过时间的要求。但是顺着枚举的思路想,能不能分步求解呢?按顺序依次求解字母的可能性例如s1[0]='a' 时能不能在k步转化为 s2[]。而到这里又出现了思维的中断,因为有两个显而易见的问题1.s1[]字符串无法被确定。2.这里求的是一个可行解的问题,与我们之前所做的dp求最小步数的问题看上去并没有任何交集。


然后这道题又给我们提供了一个重要的思路(应该更确切的说是高数给了我们一个这样的思路:在一段连续的区间[a,b]上有a<=x<y<=b 若μ为介于f[a]与f[b]之间的任何实数 至少存在一点x0∈[a,b]使得f[x0]=μ   ---> 介值性定理)相对的我们把这个思路移植到整数上如果题目中的变化是具有连续性的。我们就可以通过极值来确定是否有可行解。换而言之,现在题目的考虑方向转化为 s1[0]='a' 的情况下转化为s2[]所花的最小步数与最大步数分别是多少。这里的最大步数并非是无意义的在最小步数的基础上随意添加一个字符再删除一个字符这样的程度。 而是对于某个字符串s1[]转化为s2[]的最小步数的最大。而这些通过什么进行决定呢?通过的应该是 s1[1].s1[2].s1[3].....s1[n-1]来进行决定。那我们就考虑极端情况下的s1[]就可以了这样的话s1[]也被确定了出来,也能够求k步是否为可行解。这里Otz一下出题者。至此上面提出的两个问题全部得到完美的解决。


之后就是关于这个步数连续性的证明,这里不再赘述,转而考虑对于产生极值的s1[]的构造,如果当前需要确定的是第i个字符若使s1[i+1]s1[i+2].....s1[n-1]==s2[i+1]s2[i+2],,,s2[n-1]这样的话就相当于把问题的规模缩小到s1[0]...s1[i]因此可以视为是最好的状况也就是最小的步数解stp1,而令s1[i+1]s1[i+2].....s1[n-1]=“*********”这些不出现在原有字符串的值显然将是最大解stp2。因此只要stp1<=k<=stp2我们则说k在当前的字母下是可行的。又因为是字典序的最小所以要让处于前方的字符尽量小。而对于stp1与stp2的求解就用到了经典的dp


以上就是思路转化和求解的过程,现在再想起来还是比较清晰连贯的,很喜欢这种的题目>_<

#include <vector>
#include <list>
#include <map>
#include <set>
#include <queue>
#include <deque>
#include <stack>
#include <bitset>
#include <algorithm>
#include <functional>
#include <numeric>
#include <utility>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <ctime>

using namespace std;


class FarStrings {
public:
	int n;
	int mi(int x1,int x2){
		if (x1<x2) return (x1); return (x2);
	}
	int cal(string s,string t){
		int dp[30][30];
		s+='*';
		t+='*';
		for (int c_i=0;c_i<=n;c_i++)
			for (int c_j=0;c_j<=n;c_j++)
				if (c_i==0 && c_j==0)
					dp[c_i][c_j]=0;
				else
					dp[c_i][c_j]=65536;
		for (int c_i=0;c_i<=n;c_i++)
			for (int c_j=0;c_j<=n;c_j++)
				if (t[c_i]==s[c_j])
					dp[c_i+1][c_j+1]=mi(dp[c_i+1][c_j+1],dp[c_i][c_j]);
				else{
					dp[c_i+1][c_j+1]=mi(dp[c_i+1][c_j+1],dp[c_i][c_j]+1);
					dp[c_i+1][c_j]=mi(dp[c_i+1][c_j],dp[c_i][c_j]+1);
					dp[c_i][c_j+1]=mi(dp[c_i][c_j+1],dp[c_i][c_j]+1);
				}
		return (dp[n][n]);
	}
	bool poss(int d,string t,string s,int pos){
		int maxn=cal(s,t);
		for (int p_i=pos+1;p_i<n;p_i++)
			s[p_i]=t[p_i];
		int minn=cal(s,t);
		if (minn<=d && d<=maxn)
			return (true);
		return (false);
	}
	vector <string> find(string t) {
		n=t.size();
		vector<string> ans;
		ans.clear();
		for (int i=0;i<n;i++){
			string s(n,'?');
			for (int j=0;j<n;j++){
				s[j]='a';
				while (!poss(i+1,t,s,j))
					s[j]++;
			}
			ans.push_back(s);
		}
		return (ans);
	}
};

以上



已经博主授权,源码转载自 https://pan.quark.cn/s/a4b39357ea24 QueueForMcu 基于单片机实现的队列功能模块,主要用于8位、16位、32位非运行RTOS的单片机应用,兼容大多数单片机平台。 开源代码:https://.com/xiaoxinpro/QueueForMcu 一、特性 动态创建队列对象 动态设置队列数据缓冲区 静态指定队列元素数据长度 采用值传递的方式保存队列数据 二、快速使用 三、配置说明 目前QueueForMcu只有一个静态配置项,具体如下: 在文件 中有一个宏定义 用于指定队列元素的数据长度,默认是 ,可以根据需要更改为其他数据类型。 四、数据结构 队列的数据结构为 用于保存队列的状态,源码如下: 其中 为配置项中自定义的数据类型。 五、创建队列 1、创建队列缓存 由于我们采用值传递的方式保存队列数据,因此我们在创建队列前要手动创建一个队列缓存区,用于存放队列数据。 以上代码即创建一个大小为 的队列缓存区。 2、创建队列结构 接下来使用 创建队列结构,用于保存队列的状态: 3、初始化队列 准备好队列缓存和队列结构后调用 函数来创建队列,该函数原型如下: 参数说明: 参考代码: 六、压入队列 1、单数据压入 将数据压入队列尾部使用 函数,该函数原型如下: 参数说明: 返回值说明: 该函数会返回一个 枚举数据类型,返回值会根据队列状态返回以下几个值: 参考代码: 2、多数据压入 若需要将多个数据(数组)压入队列可以使用 函数,原理上循环调用 函数来实现的,函数原型如下: 参数说明: 当数组长度大于队列剩余长度时,数组多余的数据将被忽略。 返回值说明: 该函数将返回实际被压入到队列中的数据长度。 当队列中的剩余长度富余...
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值