计蒜客-Longest subsequence-枚举+二分

Description

String is a very useful thing and a subsequence of the same string is equally important.

Now you have a string s with length n and a string t with length m. Find out the longest subsequence in the string s so that the lexicographical order of this subsequence is strictly larger than t.

Input

two integers n, m in the first line

(All characters are lowercase letters)

The second line is a string s

The third line is a string t

  • 1≤n,m≤106

Output

Output an integer representing the longest length, otherwise output -1.

Sample Input

9 3
aaabbbccc
abc

Sample Output

6

题目大意:

有两个由小写字母构成的序列s和t,输出满足以下条件的序列的长度:
1、此序列是s的子序列。
2、此序列的字典序严格大于t。
3、此序列尽可能的长。

核心思想:

枚举+二分
用vec[i]存字母’a’+i 出现的位置。因为输入的时候是顺序输入的,所以vec[i]中的位置是升序的。

我们从字符串s中选择字符来满足条件1

怎么满足条件2呢?
假设:
有s的子序列:c??? (?表示未知)
字符串t是: cbab
那么s的子序列可以是:
1、cc??或cd??或ce??或……或cz?? 制大点是第二个字符
2、cbb?或cbc?或cbd?或……或cbz?制大点是第三个字符
3、cbac或……或cbaz 制大点是第四个字符
我们枚举字符串t的每个位置作为制大点,对应位置k
我们令s子序列的前k-1个字符和t的前k-1个字符相同,则s子序列的第k个位置可以是t[k]+1~z中的任意一个,再枚举k位置可以填的字符。(详见代码)

子序列是有前后顺序的,我们需要维护一个pos,表示匹配t的第k-1字符时用到了s的第pos个字符,那么匹配t的第k个字符时只能从s的第pos+1个字符开始选。

还有条件3需要满足。
对于枚举的位置k,上面说到从s的第pos+1个字符开始选,如果s的区间[pos+1,end]内有多个字符可作为制大点,我们要选择最左边的那个,因为s的制大点之后的所有字符都可以加入所求子序列中来扩大子序列长度。
从pos+1开始选,还要选择最左边的,vec[i]中的位置是升序的,二分,用lower_bound。

代码如下:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int N=1e6+20;
//vec[i]中存的是字母'a'+i 出现的位置 
vector<int>vec[30];
char s[N],t[N];
//在vec[z]中二分查找x之后的第一个位置 
int getid(int z,int x)
{
	int t=lower_bound(vec[z].begin(),vec[z].end(),x)-vec[z].begin();
	if(t==vec[z].size())
		return inf;
	return vec[z][t];
}
int main()
{
	int n,m;
	//输入 
	scanf("%d%d",&n,&m);
	scanf("%s%s",s,t);
	//处理字符串s 
	for(int i=0; i<n; i++)
		vec[s[i]-'a'].push_back(i);
	//遍历字符串t,枚举每个制大点 
	//ans为答案,pos为s的子序列的右端点 
	int ans=-1,pos=-1,k;
	for(k=0; k<m; k++)
	{
		int z=t[k]-'a';
		int te=inf;
		//在对应k位置的所有制大点中选择最靠左的一个
		//s中制大点后的字符都加入s子序列,使子序列最长 
		for(int j=z+1; j<26; j++)
			te=min(te,getid(j,pos+1));
		//对应k位置存在制大点,更新ans 
		if(te!=inf)
			ans=max(ans,k+n-te);
		//在s中选出位于pos右方的z字符,s的子序列向右扩展 
		pos=getid(z,pos+1);
		//无法扩展则break 
		if(pos==inf)
			break;
	}
	//遍历完t中最后一个字符
	//和t中的空字符相比,任意字符都是制大点
	//如果s中pos之后还有字符,则全部加入s子序列,更新ans 
	if(pos<n-1)
		ans=max(ans,m-1+n-pos);
	//输出 
	printf("%d\n",ans);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值