Subsequence of string s is any such string that can be obtained by erasing zero or more characters (not necessarily consecutive) from string s.
InputThe first line contains string a, and the second line — string b. Both of these strings are nonempty and consist of lowercase letters of English alphabet. The length of each string is no bigger than 105 characters.
On the first line output a subsequence of string a, obtained from b by erasing the minimum number of consecutive characters.
If the answer consists of zero characters, output «-» (a minus sign).
hi bob
-
abca accepted
ac
abacaba abcdcba
abcba
题意:两个字符串 s1 s2 长度1e5 ,问题 串2 去掉一个最短连续子串,使得剩下的串拼起来是 串1 的一个子串。
首先要理解题意,这个题是删除 一段 连续 的子串,串b的三部分都是连续的,问剩下的两串拼起来是不是串1的一个子串,如果最暴力的枚举删除的区间o(n3)复杂度。。肯定不行,这里学习了前缀后缀不一定只是记录某个数的和的,也可以像这个题这么用。。
思路:记录串a每个字符,是串b最长的属于串a这个字符之前的子序列坐标的最后一个,比如 串a : ababa 串 b: abc, 那么pre【1】 = 1pre【2】 = 2, pre【3】 = 2, pre【4】 = 2 ,pre【3】= 2指的是 串b 中 ab 是串a前三个的字符的子序列,同理记录串a的后缀,这样的目的是,很方便的知道每个字符 往前对应几个b 往后对应几个b,要删除的那一串字符就是 suf - pre,不用暴力枚举了,这里因为这三个串都是连续的所以可以这样前缀后缀,在suf-pre的时候,有的题解是二分来找,其实不用,因为是前缀,pre【i+1】 肯定 >= pre[i],suf[i-1] >= suf[i],而且又要求是最短的所以suf[i] - pre[i-1]肯定是最短的,枚举i的时候,pre【i-1】肯定是前i个最大的。。所以直接-pre【i-1】就行。。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
using namespace std;
const int maxn = 1e5 + 5;
int pre[maxn], suf[maxn];
char a[maxn], b[maxn];
int main()
{
while(~scanf("%s", a+1))
{
scanf("%s", b+1);
int l1 = strlen(a+1), l2 = strlen(b+1), last = 0; //这一步很重要,这里是预防,串1还没枚举玩,串b已经枚举完了,串b枚举完了,代表还没有枚举的串1前缀都是串b最后一个,后缀都是串1第一个
for(int i = 1; i <= l1; i++)
pre[i] = l2, suf[i] = 1;
suf[l1+1] = l2+1; //这个是为了找删去最短子串做准备,因为是-pre【i-1】,所以要枚举到l2+1
for(int i = 1, j = 1; i <= l1 && j <= l2; i++) //这里是构造前缀
{
if(a[i] == b[j])
pre[i] = j, last = j, j++; //如果相同,就看下一个串b的字符,更新last
else
pre[i] = last; //如果这个不相同,就等于前面的last
}
last = l2+1; //这里的初始化是l2+1,代表后缀是比l2的长度多1,也就是没有一个相同的
for(int i = l1, j = l2; i >= 1 && j >= 1; i--)
{
if(a[i] == b[j])
suf[i] = j, last = j, j--;
else
suf[i] = last;
}
int ans = l2+1, l, r; //ans 初始化时l2+1,因为suf最多是l2+1(代表一个相同的也没有),pre最小是0(代表一个相同的也没有),ans = suf - pre 所以最大就是l2+1
// cout << ans << endl;
for(int i = 1; i <= l1+1; i++) //这里是枚举删除的最短的字符串,记住要枚举到l1+1,因为-pre【i-1】
{
// cout << suf[i] << ' ' << pre[i-1] << endl;
if(pre[i-1] < suf[i]) //枚举suf【i】,pre【i-1】肯定是前i-1个中最大的,所以直接-它就行,而且首先要确保pre[i-1] < suf[i],因为这样才能“删除”
{
if(ans > suf[i]-pre[i-1]) //这里是更新
{
ans = suf[i] - pre[i-1];
// cout << suf[i] << ' ' << pre[i-1] << endl;
l = pre[i-1]; //这是记录删除子串的位置
r = suf[i];
}
}
}
if(ans == l2+1) //如果没有更新过就输出
{
cout << "-" << endl;
continue;
}
for(int i = 1; i <= l2; i++) //输出没删除的子串
{
if(i <= l || i >= r)
cout << b[i];
}
}
return 0;
}