更新历史

更新历史

2023-09-18

发布博客

洛谷传送门

CF 传送门

题意

给你两个字符串 aabb,你可以在 bb 中删去尽量短的子段,使得 bbaa 的子序列。求出最后的 bb

思路

真是奇了怪了,这种题洛谷题解里竟然没有双指针的做法?

首先考虑判断一个字符串 bb 是否是另一个字符串 aa 的子序列。这个问题相信学过点 OI 的人都会做。

其实在做上面这个题的时候,因为贪心,bb 字符串的最右边字符对应在 aa 中一定是最靠左的。那么很容易,也可以求出 bb 的最左边字符在 aa 中最靠右的位置。

现在再看原题,其实就是把 bb 字符串分成前后两个部分,其中前一个部分在 aa 中尽量靠左,后一个部分在 aa 中尽量靠右,并且两个不相交。

然后就好做了,先对 bb 的每一个前缀求出「最右边字符对应在 aa 中最靠左的位置」,每一个后缀求出「最左边字符对应在 aa 中最靠右的位置」,分别用 LLRR 数组保存。

最后我们要选出两个断点 llrr,分别代表分成的前后两个部分的右端点和左端点,并且 Ll<RrL_l<R_r,同时 rlr-l 要尽量的小。

所以这个怎么求呢?等等, LLRR 单调递增?

那不是妥妥的双指针吗?

然后这题就做完了,时间复杂度 O(n)O(n)

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <bits/stdc++.h>
using namespace std;
int n,m;
string a,b;
int L[100005],R[100005];
int ans=1e9,k1,k2;//最短r-l,以及两个断点的记录
int main(){
cin>>a>>b;
n=a.length(),m=b.length();
a=" "+a,b=" "+b;
int r=1;//指针
for(int i=1;i<=m;i++){//预处理L数组
while(r<=n&&a[p]!=b[i]) r++;
L[i]=r,r=min(r+1,n+1);
}
r=n;
for(int i=m;i>=1;i--){//预处理R数组
while(r>=1&&a[p]!=b[i]) r--;
R[i]=r,r=max(r-1,0);
}
if(L[1]==n+1&&R[m]==0){//这里特判一下,或者在最后特判也行
printf("-\n");
return 0;
}
L[0]=0,R[m+1]=n+1;//防越界特判的操作
r=1;
for(int l=0;l<=m;i++){//计算答案,双指针板板
if(L[l]==n+1) break;
r=max(r,l+1);
while(L[l]>=R[r]){
r++;
}
if(r-l-1<ans){
ans=r-l-1,k1=l,k2=r;
}
}
for(int i=1;i<=m;i++){
if(i<=k1||i>=k2){
cout<<b[i];
}
}
printf("\n");
return 0;
}