TC-SRM617div1-800

这道题的题意就是:已知字符串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);
	}
};

以上



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值