原文链接: Codeforces 762C Two strings
上一篇: POJ 3977 折半枚举+二分搜索
下一篇: CodeForces 797C
• 给定两个字符串A,B
• 再B中删除最少的连续字符(一段字符),使得B成为A的子序列
• 1 ≤ |A|, |B| ≤ 1e5
二分+预处理前缀和后缀
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <algorithm>
#include <cmath>
#include <vector>
#include <cstring>
#include <stdio.h>
using namespace std;
typedef long long ll;
const int MAXN = 1e5 + 155;
int pre[MAXN], suf[MAXN];//存前缀和后缀 相当于存 A的 对于B的子序列的长度
char a[MAXN], b[MAXN];
int main(){
while(~scanf("%s%s", a + 1, b + 1)){
int len1 = strlen(a + 1);
int len2 = strlen(b + 1);
int last = 0;
// 不论是前缀还是后缀, 只要A 中有B的 某些串就可以
//可以不连续, 例如A :abc B: ac B为A的子串
for(int i = 1 ; i <= len1 ; i++) //赋初值
pre[i] = len2, suf[i] = 1;
suf[len1 + 1] = len2 + 1;// 必须有 目的是为了删除子串的作用, pre[i-1]
for(int i = 1, j = 1 ; i <= len1 && j <= len2 ; i++){//这里是构造A的前缀
if(a[i] == b[j]){// 相同记录前缀, 更新
pre[i] = j;
last = j;
j++;
} else
pre[i] = last; //如果这个不相同,就等于前面的last
}
last = len2 + 1;
for(int i = len1, j = len2 ; i >= 1 && j >= 1 ; i--){//构造A的后缀
if(a[i] == b[j]){
suf[i] = j;
last = j;
j--;
} else
suf[i] = last;
}
int ans = len2 + 1, left = 1, right = len2 + 1;//初始化答案为最大;
for(int i = 1 ; i <= len1 + 1 ; i++){//枚举所有的情况
if(pre[i - 1] < suf[i] && ans > suf[i] - pre[i - 1]){
ans = suf[i] - pre[i - 1];
left = pre[i - 1];
right = suf[i];
}
}
if(ans == len2 + 1){
cout << "-" << endl;
continue;
}
for(int i = 1 ; i <= len2 ; i++){// 输出 删除后的b
if(i <= left || i >= right)
cout << b[i];
}
}
return 0;
}
题目大意:
给定两个字符串a,b (len≤105),让你去b中的一个连续的字段,使剩余的b串中的拼接起来的两个串是a穿的子序列。最大化这个字串的长度。
题解:
删除这个操作不太好说,我们先换一个思路:实际上删除就是在b串中分别取出两个不相交的前缀和后缀,使得这两个串在a串中不重叠地出现
我们发现答案具有显然的单调性,所以我们首先二分答案(二分删去的字串长度)。
现在来考虑如何进行判定:
一个很直接的思路就是枚举所有的符合条件的前缀和后缀
然后对这个前缀求其在a中的最靠左的子序列的右端下标。
相应的后缀也这么处理.
如: a = "abbcbc",pre = "abc" [数组下标从1开始]
那么串pre,在a中最靠左的子序列有段下标为4.
那么我们我们只要找到一个点,从这个点劈开后,前缀的下标 < 后继的下标即可
枚举 O(n)求出在a序列中拓展到的位置 O(n),总时间复杂度为 O(n2logn)
TLE
我们发现每次对一个前缀求在a中出现的下标都是重复的问题
所以我们 O(n)预处理一遍(后缀是类似的)
所以我们就做到了 O(n)判定
时间复杂度
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 200010;
char a[maxn], b[maxn];
int pre[maxn], suf[maxn];
int main(){
scanf("%s%s", a + 1, b + 1);
int n = strlen(a + 1);
int m = strlen(b + 1);
for(int i = 1, j = 0 ; b[i] ; ++i){
++j;
while(b[i] != a[j] && j <= n) ++j;
pre[i] = j;
}
for(int i = m, j = n + 1 ; b[i] ; --i){
--j;
while(b[i] != a[j] && j > 0) --j;
suf[i] = j;
}
suf[m + 1] = n + 1;
int ans1, ans2, l = 0, r = m;
while(l <= r){
int mid = l + r >> 1, i;
for(i = 0 ; i + mid <= m ; ++i) if(pre[i] < suf[i + mid + 1]) break;
if(i + mid <= m){
ans1 = i, ans2 = i + mid + 1, r = mid - 1;
} else l = mid + 1;
}
if(ans2 - ans1 > m) putchar('-');
else {
for(int i = 1 ; i <= ans1 ; ++i) putchar(b[i]);
for(int i = ans2 ; b[i] ; ++i) putchar(b[i]);
}
getchar(); getchar();
return 0;
}