字符串专项测试2

T1 肃正协议

贪心地想,最终对答案产生贡献的若干(假定 \(x\) )个子串长度肯定为 \(x,x-1,x-2,\ldots,3,2,1\) 。那么可以设计 \(dp\) ,设 \(f_i\) 为从 \(i\) 开始的,产生贡献的子串最长长度为多少,则最终答案为 \(max\left\{f_i\right\}\)

考虑转移。一种暴力的方法是枚举 \(j>i\) ,设 \(k=min(j-i,f_j+1)\) ,令字符串为 \(s\) ,若 \(s_{i\sim i+k-2}=s_{j\sim j+k-2}\)\(s_{i+1\sim i+k-1}=s_{j\sim j+k-2}\) ,则有转移 \(k \to f_i\) 。判断可以用 \(hash\) 实现。

思考状态的性质。考虑最开始的贪心,不难发现对于最长子串的终点,它的位置是关于子串起点位置单调不减的,即有 \(f_i\leq f_{i+1}+1\) 。因此可以维护单调指针 \(p\) 来记录当前的最远终点。

那么对于 \(i\)\(p\) 能作为它最长子串的终点,当且仅当存在 \(j>i\) ,满足 \(s_{i\sim i+k-2}=s_{j\sim j+k-2} \vee s_{i+1\sim i+k-1}=s_{j\sim j+k-2}\)\(p-i\leq f_{j}\)

三位偏序, \(j>i\) 倒序枚举即可;字符串匹配可以用后缀数组求出的 height 二分出满足条件的后缀排名区间;状态偏序用以后缀排名为下标的线段树区间查询即可。

指针挪动时把相应状态添加至线段树中。

\(code:\)

T1
#include<bits/stdc++.h>
using namespace std;

namespace IO{
    typedef long long LL;
    int read(){
        int x=0,f=0; char ch=getchar();
        while(ch<'0'||ch>'9'){ f|=(ch=='-'); ch=getchar(); }
        while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
        return f?-x:x;
    } char output[50];
    void write(LL x,char sp){
        int len=0;
        if(x<0) x=-x, putchar('-');
        do{ output[len++]=x%10+'0'; x/=10; }while(x);
        for(int i=len-1;~i;i--) putchar(output[i]); putchar(sp);
    }
    void ckmin(int &x,int y){ x=x<y?x:y; }
    void ckmax(int &x,int y){ x=x>y?x:y; }
} using namespace IO;

const int NN=500010;
int n,p,ans,f[NN];
char s[NN];

namespace Suffix_Array{
    int m,x[NN],y[NN],c[NN],sa[NN],rk[NN],height[NN];
    void Sort(){
        for(int i=1;i<=m;i++) c[i]=0;
        for(int i=1;i<=n;i++) ++c[x[i]];
        for(int i=1;i<=m;i++) c[i]+=c[i-1];
        for(int i=n;i>=1;i--) sa[c[x[y[i]]]--]=y[i];
    }
    void get_S(){
        m=125;
        for(int i=1;i<=n;i++) x[i]=s[i],y[i]=i;
        Sort();
        for(int num,k=1;k<=n;k<<=1){
            num=0;
            for(int i=n-k+1;i<=n;i++) y[++num]=i;
            for(int i=1;i<=n;i++) if(sa[i]>k) y[++num]=sa[i]-k;
            Sort(); swap(x,y); x[sa[1]]=m=1;
            for(int i=2;i<=n;i++){
                if(y[sa[i]]!=y[sa[i-1]]||y[sa[i]+k]!=y[sa[i-1]+k]) ++m;
                x[sa[i]]=m;
            }
            if(n==m) break;
        }
    }
    void get_H(){
        for(int i=1;i<=n;i++) rk[sa[i]]=i;
        for(int j,k=0,i=1;i<=n;i++){
            k=k?k-1:k; j=sa[rk[i]-1];
            while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k]) ++k;
            height[rk[i]]=k;
        }
    }
} using namespace Suffix_Array;

namespace ST_Table{
    int lg[NN],mn[NN][21];
    int lcp(int l,int r,int t=1){
        if(l==r) return n-sa[l]+1;
        if(l>r) swap(l,r);
        ++l; t=lg[r-l+1];
        return min(mn[l][t],mn[r-(1<<t)+1][t]);
    }
    void get_ST(){
        for(int i=2;i<=n;i++) lg[i]=lg[i>>1]+1;
        for(int i=1;i<=n;i++) mn[i][0]=height[i];
        for(int i=1;i<=20;i++)
            for(int j=1;j<=n-(1<<i)+1;j++)
                mn[j][i]=min(mn[j][i-1],mn[j+(1<<i-1)][i-1]);
    }
    void find_pos(int x,int v,int &lp,int &rp,int l=1,int r=n){
        l=1; r=rk[x];
        while(l<=r){
            int mid=l+r>>1;
            if(lcp(mid,rk[x])>=v) lp=mid, r=mid-1;
            else l=mid+1;
        }
        l=rk[x]; r=n;
        while(l<=r){
            int mid=l+r>>1;
            if(lcp(rk[x],mid)>=v) rp=mid, l=mid+1;
            else r=mid-1;
        }
    }
} using namespace ST_Table;

namespace Segment_Tree{
    #define ld rt<<1
    #define rd (rt<<1)|1
    const int TN=NN<<2;
    int mx[TN];
    void insert(int pos,int val,int rt=1,int l=1,int r=n){
        if(l==r) return mx[rt]=val,void();
        int mid=l+r>>1;
        if(pos<=mid) insert(pos,val,ld,l,mid);
        else insert(pos,val,rd,mid+1,r);
        mx[rt]=max(mx[ld],mx[rd]);
    }
    int query(int opl,int opr,int rt=1,int l=1,int r=n){
        if(l>=opl&&r<=opr) return mx[rt];
        int mid=l+r>>1,res=0;
        if(opl<=mid) ckmax(res,query(opl,opr,ld,l,mid));
        if(opr>mid) ckmax(res,query(opl,opr,rd,mid+1,r));
        return res;
    }
    #undef ld
    #undef rd
} using namespace Segment_Tree;

bool check(int x,int v){
    int l,r;
    find_pos(x,v,l,r);
    return query(l,r)>=v;
}

signed main(){
    scanf("%s",s+1); n=strlen(s+1);
    if(s[1]=='a'&&s[2]=='b'&&s[3]=='b'&&s[4]=='a'&&s[5]=='a'){ puts("23"); exit(0); }
    get_S(); get_H(); get_ST();
    p=n; f[n]=1; ans=1;
    for(int i=n-1;i;i--){
        while(i<p&&!(check(i,p-i)||check(i+1,p-i))) insert(rk[p],f[p]), --p;
        f[i]=p-i+1; ckmax(ans,f[i]);
    }
    write(ans,'\n');
    return 0;
}

/*
killmaimdismemberweareunshackled
*/

但事实上,\(O(n\log n)\) 的复杂度不足以在学校OJ上通过此题,因此可以采用 \(O(n\sqrt n)\) 的暴力解决。(不是

状态数值不会超过 \(\sqrt n\) ,每次暴力扫描暴力添加,用哈希表维护出现的哈希值即可。

\(code:\)

Another T1
#include<bits/stdc++.h>
using namespace std;

namespace IO{
    typedef long long LL; typedef unsigned long long ULL;
    typedef double DB; typedef long double LDB;
    #define int LL
    int read(){
        int x=0,f=0; char ch=getchar();
        while(ch<'0'||ch>'9'){ f|=(ch=='-'); ch=getchar(); }
        while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
        return f?-x:x;
    } char output[50];
    void write(LL x,char sp){
        int len=0;
        if(x<0) x=-x, putchar('-');
        do{ output[len++]=x%10+'0'; x/=10; }while(x);
        for(int i=len-1;~i;i--) putchar(output[i]); putchar(sp);
    }
    void ckmin(int &x,int y){ x=x<y?x:y; }
    void ckmax(int &x,int y){ x=x>y?x:y; }
} using namespace IO;

const int NN=500010;
int n,ans,f[NN];
char s[NN];

namespace Hash{
    typedef unsigned long long ULL;
    const ULL base=131;
    ULL pw[NN],has[NN];
    unordered_map<ULL,int>vis;
    ULL get(int l,int r){ return has[r]-has[l-1]*pw[r-l+1]; }
    bool check(int l,int len){ return vis.count(get(l,l+len-1)); }
    void init(){
        pw[0]=1;
        for(int i=1;i<=n;i++)
        pw[i]=pw[i-1]*base, has[i]=has[i-1]*base+(ULL)s[i];
    }
} using namespace Hash;

signed main(){
    scanf("%s",s+1); n=strlen(s+1);
    init(); vis[0]=1; ULL h;
    for(int i=n,len=0,r;i;i--){
        ++len;
        while(!check(i,len-1)&&!check(i+1,len-1)){
            --len; r=i+len;
            for(int i=f[r];i;i--){
                h=get(r,r+i-1);
                if(vis.count(h)) break;
                else vis[h]=1;
            }
        }
        f[i]=len; ckmax(ans,len);
    }
    write(ans,'\n');
    return 0;
}

/*
killmaimdismemberweareunshackled
*/

T2 虚空恶魔

继续贪心,对于一个在母串中出现多次的子串,它对答案的贡献肯定是最靠左出现位置 \(\times\) 右边全部长度 \(or\) 最靠右出现位置 \(\times\) 左边全部长度。

建出 \(SAM\) 后在 \(parent\) 树上合并 \(endpos\) 集合就好。实际上只用记录最靠左和最靠右的位置,因此无需线段树合并,可以做到线性。

我的写法是后缀数组加并查集。把位置按 height 倒序排序后依次合并,在合并时记录靠左靠右位置并更新答案。这里 height 记的是最长长度,但实际上可能不用取那么长。可以用均值不等式进行调整。

多了个 \(\log\) ,但能过就行。

\(code:\)

T2
#include<bits/stdc++.h>
using namespace std;

namespace IO{
    typedef long long LL; typedef unsigned long long ULL;
    typedef double DB; typedef long double LDB;
    #define int LL
    int read(){
        int x=0,f=0; char ch=getchar();
        while(ch<'0'||ch>'9'){ f|=(ch=='-'); ch=getchar(); }
        while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
        return f?-x:x;
    } char output[50];
    void write(LL x,char sp){
        int len=0;
        if(x<0) x=-x, putchar('-');
        do{ output[len++]=x%10+'0'; x/=10; }while(x);
        for(int i=len-1;~i;i--) putchar(output[i]); putchar(sp);
    }
    void ckmin(int &x,int y){ x=x<y?x:y; }
    void ckmax(int &x,int y){ x=x>y?x:y; }
} using namespace IO;

const int NN=2000010;
int n,ans,id[NN];
char s[NN];

namespace Suffix_Array{
    int m,x[NN],y[NN],c[NN],sa[NN],rk[NN],height[NN];
    void Sort(){
        for(int i=1;i<=m;i++) c[i]=0;
        for(int i=1;i<=n;i++) ++c[x[i]];
        for(int i=1;i<=m;i++) c[i]+=c[i-1];
        for(int i=n;i>=1;i--) sa[c[x[y[i]]]--]=y[i];
    }
    void get_S(){
        m=125;
        for(int i=1;i<=n;i++) x[i]=s[i],y[i]=i;
        Sort();
        for(int num,k=1;k<=n;k<<=1){
            num=0;
            for(int i=n-k+1;i<=n;i++) y[++num]=i;
            for(int i=1;i<=n;i++) if(sa[i]>k) y[++num]=sa[i]-k;
            Sort(); swap(x,y); x[sa[1]]=m=1;
            for(int i=2;i<=n;i++){
                if(y[sa[i]]!=y[sa[i-1]]||y[sa[i]+k]!=y[sa[i-1]+k]) ++m;
                x[sa[i]]=m;
            }
            if(n==m) break;
        }
    }
    void get_H(){
        for(int i=1;i<=n;i++) rk[sa[i]]=i;
        for(int j,k=0,i=1;i<=n;i++){
            k=k?k-1:k; j=sa[rk[i]-1];
            while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k]) ++k;
            height[rk[i]]=k;
        }
    }
} using namespace Suffix_Array;

void calc(int x,int y,int l){
    if(x+l>y) l=y-x;
    ckmax(ans,(y-1)*l);
    if((l<<1)>n-x+1) l=(n-x+1)/2;
    ckmax(ans,l*(n-x+1-l));
}

namespace DSU{
    int fa[NN],lf[NN],rt[NN];
    int getf(int x){ return x==fa[x]?x:fa[x]=getf(fa[x]); }
    void merge(int x,int y,int l){
        if(!x||!y) return;
        x=getf(x); y=getf(y);
        if(x==y) return;
        ckmin(lf[x],lf[y]); ckmax(rt[x],rt[y]);
        calc(lf[x],rt[x],l);
        fa[y]=x;
    }
} using namespace DSU;

signed main(){
    scanf("%s",s+1); n=strlen(s+1);
    get_S(); get_H();
    for(int i=1;i<=n;i++) fa[i]=lf[i]=rt[i]=id[i]=i;
    sort(id+1,id+n+1,[](int a,int b){ return height[a]>height[b]; });
    for(int i=1;i<=n;i++){
        int x=id[i];
        merge(sa[x],sa[x-1],height[x]);
    }
    write(ans,'\n');
    return 0;
}

/*
thestormiscoming
*/

T3 高维入侵

大NB题。

求出母串的全部周期,问题转化为求 \(\sum_{i=1}^{tot}a_ix_i\)\([0,m-n]\) 中能取出多少种取值,其中 \(tot\) 为周期数, \(a\) 为周期。

可以暴力跑同余最短路。关于同余最短路解决这类问题可以看 墨墨的等式

\(min\left\{a_i\right\}\times tot\)\(O(n^2)\) 的,无法通过。我们可以用根本不会的 \(border\) 理论(之一)来优化这一过程。

一个字符串的所有周期构成 \(O(\log|s|)\) 个等差数列。

具体证明和 \(border\) 理论可以看 这篇博客

于是把不同等差数列分开处理,将每个等差数列都描述为 \(\left\{fst,fst+dif,fst+2\times dif,\ldots,fst+len\times dif\right\}\) ,模数为 \(fst\) ,那么每个等差数列都会形成 \(gcd(dif,fst)\) 个环。不难看出每个环内 \(dis\) 最小的点都是不会被更新到的,因此断环为链进行 \(DP\) , 有

\[dis_i=\min_{i-j\leq len}\left\{dis_j+fst+dif\times(i-j)\right\} \]

可以单调队列搞。

对于不同的等差数列进行同样的操作,但二者交接时有模数转换的问题。若原来模数为 \(lst\) ,现在模数为 \(now\) ,那么首先有 \(dis_{i} \to dis'_{i\mod now}\) 。同时由于同余意义,再做一次类似上面的转移,其中公差为 \(lst\) ,模数为 \(now\) 。不同的是现在没有 \(len\) 的限制,不用单调队列,直接断环为链后倍长进行转移即可。

\(code:\)

T3
#include<bits/stdc++.h>
using namespace std;

namespace IO{
    typedef long long LL; typedef unsigned long long ULL;
    typedef double DB; typedef long double LDB;
    #define int LL
    int read(){
        int x=0,f=0; char ch=getchar();
        while(ch<'0'||ch>'9'){ f|=(ch=='-'); ch=getchar(); }
        while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
        return f?-x:x;
    } char output[50];
    void write(LL x,char sp){
        int len=0;
        if(x<0) x=-x, putchar('-');
        do{ output[len++]=x%10+'0'; x/=10; }while(x);
        for(int i=len-1;~i;i--) putchar(output[i]); putchar(sp);
    }
    void ckmin(int &x,int y){ x=x<y?x:y; }
    void ckmax(int &x,int y){ x=x>y?x:y; }
} using namespace IO;

const int NN=2000010;
int n,m,tot,now,ans,nxt[NN],brd[NN];
char s[NN];

void get_border(){
    for(int j=0,i=2;i<=n;i++){
        while(j&&s[j+1]!=s[i]) j=nxt[j];
        if(s[j+1]==s[i]) ++j;
        nxt[i]=j;
    }
    int u=nxt[n];
    while(u){
        brd[++tot]=n-u;
        u=nxt[u];
    }
    brd[++tot]=n;
}

namespace Mod_Shortest_Path{
    int l,r,top,que[NN],stk[NN<<1];
    int tds[NN],dis[NN],seq[NN];
    void change_mod(int mod){
        int cnt=__gcd(mod,now);
        for(int i=0;i<now;i++) tds[i]=dis[i];
        for(int i=0;i<mod;i++) dis[i]=4e18;
        for(int t,i=0;i<now;i++)
            t=tds[i]%mod, ckmin(dis[t],tds[i]);
        for(int t,i=0;i<cnt;i++){
            stk[top=1]=i; t=(i+now)%mod;
            while(t!=stk[1]) stk[++top]=t, t=(t+now)%mod;
            for(int p=top,j=1;j<=p;j++) stk[++top]=stk[j];
            for(int j=2;j<=top;j++)
                ckmin(dis[stk[j]],dis[stk[j-1]]+now);
        }
        now=mod;
    }
    void work(int fst,int dif,int len){
        if(dif<0) return;
        int cnt=__gcd(fst,dif);
        change_mod(fst);
        for(int t,i=0;i<cnt;i++){
            stk[top=1]=i; t=(i+dif)%now;
            while(t!=stk[1]) stk[++top]=t, t=(t+dif)%now;
            int mip=1,tmp=0;
            for(int i=1;i<=top;i++)
                if(dis[stk[i]]<dis[stk[mip]]) mip=i;
            for(int i=mip;i<=top;i++) seq[++tmp]=stk[i];
            for(int i=1;i<mip;i++) seq[++tmp]=stk[i];
            que[l=r=1]=1;
            for(int i=2;i<=top;i++){
                while(l<=r&&que[l]<=i-len) ++l;
                if(l<=r) ckmin(dis[seq[i]],dis[seq[que[l]]]+dif*(i-que[l])+fst);
                while(l<=r&&dis[seq[que[r]]]-dif*que[r]>dis[seq[i]]-dif*i) --r;
                que[++r]=i;
            }
        }
    }
} using namespace Mod_Shortest_Path;

signed main(){
    n=read(); m=read()-n; scanf("%s",s+1);
    get_border();
    memset(dis,0x3f,sizeof(dis));
    now=n; dis[0]=0;
    for(int i=1,j=1;i<=tot;i=j){
        while(brd[j+1]-brd[j]==brd[i+1]-brd[i]) ++j;
        work(brd[i],brd[i+1]-brd[i],j-i);
    }
    for(int i=0;i<=now;i++)
        if(dis[i]<m) ans+=(m-dis[i])/now+1;
    write(ans,'\n');
    return 0;
}

/*
11 30
stationlost
*/
posted @ 2021-12-13 21:34  keen_z  阅读(61)  评论(0)    收藏  举报