zoukankan      html  css  js  c++  java
  • CF1203D2 Remove the Substring (hard version) 题解

    这题初赛让我白给了6分,于是我决定回来解决一下它。

    说实话,看原题题面和看CCF代码真是两种完全不同的感受……

    ------------
    思路分析:

    把$s$串删去一部分之后,会把$s$串分成两部分,当然其中一部分有可能为空。$t$串作为$s$串的字串,在删去一部分之后也会被分为两部分。因此我们可以枚举$t$串被分开的位置,然后进行计算。

    设$t$串在$t[p]$和$t[p+1]$之间被分开,$s$串在$s[i]$和$s[i+1]$之间被分开。因为要使答案最大,因此我们要让$s$串的左半部分包含且仅包含一个$t$串的左半部分,右半部分也是一样。

    因此,按照CCF的讲法,设$s$串和$t$串的长度分别为$slen$和$tlen$,我们可以设$head[i]=p$表示$s[0...i]$包含且仅包含一个子串为$t[0...p]$,设$back[i]=p$表示$s[i...slen-1]$包含且仅包含一个字串为$t[p...tlen-1]$。

    如何递推$head$和$back$数组?很显然了,用两个指针分别指向$s$串和$t$串,表示当前位置,相同即匹配成功。

    因为指针初值需要设置为0,因此以下代码将$s$串和$t$串都整体后移了一位。

    p=1;
    for(int i=1;i<s.size();i++)
    {
        head[i]=head[i-1];
        if(s[i]==t[p])
            head[i]=p++;
    }
    p=t.size()-1;back[s.size()]=t.size();
    for(int i=s.size()-1;i;i--)
    {
        back[i]=back[i+1];
        if(s[i]==t[p])
            back[i]=p--;
    }

    递推出$head$和$back$数组后,枚举断点计算答案就行了。用两个指针$i,j$分别指向删除的部分两端。为了让答案最大,应该找到满足$head[i]=p$的最小的$i$,以及满足$back[j]=p+1$的最大的$j$,这两个步骤用两个while循环就可以轻松搞定了。有一个需要注意的点,当被分开的两部分其中一部分包含整个$t$串时,另一部分取空串是最优的,这个需要特判一下。答案即为$j-i-1$。计算答案时也要特判不合法的情况。各个指针的初值也需要注意。

    #include<iostream>
    #include<cstdio>
    #include<string>
    using namespace std;
    const int N=1e6;
    int p=1,ans;
    int head[N],back[N];
    string s,t;
    int main()
    {
        cin>>s>>t;s='.'+s,t='.'+t;//后移一位
        for(int i=1;i<s.size();i++)
        {
            head[i]=head[i-1];
            if(s[i]==t[p])
                head[i]=p++;
        }
        p=t.size()-1;back[s.size()]=t.size();
        for(int i=s.size()-1;i;i--)
        {
            back[i]=back[i+1];
            if(s[i]==t[p])
                back[i]=p--;
        }
        p=0;
        for(int i=0,j=1;i<s.size(),j<s.size(),p<t.size();p++)
        {
            while(head[i]<p && i<s.size())
                i++;
            while(back[j+1]<=p+1 && j<s.size())
                j++;//找到最优的i和j
            if(p==t.size()-1)
                j=s.size();//贪心地让另一部分为空串
            if(i<s.size() && j<=s.size())//合法才更新答案
                ans=max(ans,j-i-1);
        }
        printf("%d",ans);
        return 0;
    }

    因为指针$i,j$一定是递增的,因此时间复杂度$O(n)$。

    ------------
    既然是初赛原题,那么再来看看CCF的代码:(实测比我的代码快15ms...不过我的代码有的地方常数的确比较大)

    #include<iostream>
    #include<string>
    using namespace std;
    const int maxl=1e6;//改了一下数组大小,可以过困难版
    string s,t;
    int pre[maxl],suf[maxl];//分别相当于head和back数组
    
    int main(){
        cin>>s>>t;
        int slen=s.length(),tlen=t.length();
        for(int i=0,j=0;i<slen;++i){
            if(j<tlen && s[i]==t[j]) ++j;
            pre[i]=j;
        }
        for(int i=slen-1,j=tlen-1;i>=0;--i){
            if(j>=0 && s[i]==t[j]) --j;
            suf[i]=j;
        }
        suf[slen]=tlen-1;//递推基本相同,表示略有区别
        int ans=0;
        for(int i=0,j=0,tmp=0;i<=slen;++i){
            while(j<=slen && tmp>=suf[j]+1) ++j;
            ans=max(ans,j-i-1);
            tmp=pre[i];
        }
        cout<<ans<<endl;
        return 0;
    }

    可以发现思路实际上大体相同,但是计算答案时略有差别。

    分析一下这个计算答案的过程,枚举$s$的断点。可以发现,对于当前的循环,$tmp=pre[i-1]$,即$t[0...tmp-1]$是$s[0...i-1]$的字串,那么接下来为了使答案最大,应该找到满足$t[tmp...tlen-1]$是$s[j...slen-1]$的$j$。而这份代码中的while循环找到的$j$应该会比我们要找的$j$大1,因此要删除的部分就是$s[i...j-2]$,长度$j-i-1$。

    然后分析一下题目:(理解了题意之后感觉想错都难qwq)

    1.

    Q:程序输出时,suf数组满足:对于任意$0leq ileq slen$,$suf[i]leq suf[i+1]$。

    A:T,显然$suf$数组是递增的。

    2.

    Q:当$t$是$s$的子序列时,输出一定不为0。

    A:F,反例样例3。

    3.

    Q:程序运行到第23行时,“j-i-1”一定不小于0。

    A:F,在本题中的确不会出现这种情况,但初赛题目中可没保证$t$是$s$的子串,$t$不是$s$的字串时就会出现这种情况。

    4.

    Q:当$t$是$s$的子序列时,$pre$数组和$suf$数组满足:对于任意$0leq i<slen$,$pre[i]>suf[i+1]+1$。

    A:F,反例样例1。

    5.

    Q:若$tlen=10$,输出为0,则$slen$最小为( )。

    A:输出为0说明$t$不是$s$的子序列或者$s=t$,显然前者可以使答案更小,即$t$越短越好。由于cin不能输入空串,因此最短只能是1。

    6.

    Q:若$tlen=10$,输出为2,则$slen$最小为( )。

    A:$s$最多删去两个字符使$t$仍是$s$的字串,显然$t$串最短长度为12。

    最后祝大家CSP-J/S 2019rp++。

  • 相关阅读:
    Java面试之最常见的十道面试题(超经典)
    hdu 3939(勾股+容斥)
    poj 1845 (逆元 + 约数和)
    hdu 5607 BestCoder Round #68 (矩阵快速幂)
    中国剩余定理
    Math
    (⊙o⊙)…
    lucas定理
    hdu 5600 BestCoder Round #67 (div.2)
    hdu5601 BestCoder Round #67 (div.2)
  • 原文地址:https://www.cnblogs.com/TEoS/p/11773920.html
Copyright © 2011-2022 走看看