更新历史 
              
             
洛谷传送门
CF 传送门
题意
给你两个字符串 a 和 b,你可以在 b 中删去尽量短的子段,使得 b 是 a 的子序列。求出最后的 b。
思路
真是奇了怪了,这种题洛谷题解里竟然没有双指针的做法?
首先考虑判断一个字符串 b 是否是另一个字符串 a 的子序列。这个问题相信学过点 OI 的人都会做。
其实在做上面这个题的时候,因为贪心,b 字符串的最右边字符对应在 a 中一定是最靠左的。那么很容易,也可以求出 b 的最左边字符在 a 中最靠右的位置。
现在再看原题,其实就是把 b 字符串分成前后两个部分,其中前一个部分在 a 中尽量靠左,后一个部分在 a 中尽量靠右,并且两个不相交。
然后就好做了,先对 b 的每一个前缀求出「最右边字符对应在 a 中最靠左的位置」,每一个后缀求出「最左边字符对应在 a 中最靠右的位置」,分别用 L 和 R 数组保存。
最后我们要选出两个断点 l 和 r,分别代表分成的前后两个部分的右端点和左端点,并且 Ll<Rr,同时 r−l 要尽量的小。
所以这个怎么求呢?等等, L 和 R 单调递增?
那不是妥妥的双指针吗?
然后这题就做完了,时间复杂度 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; 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++){         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--){         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; }
   |