其他分享
首页 > 其他分享> > 【简要题解】Hihocoder 重复旋律1-8简要题解

【简要题解】Hihocoder 重复旋律1-8简要题解

作者:互联网

【简要题解】Hihocoder 重复旋律1-8简要题解

编号 名称标签 难度
1403 后缀数组一·重复旋律 Lv.4
1407 后缀数组二·重复旋律2 Lv.4
1415 后缀数组三·重复旋律3 Lv.4
1419 后缀数组四·重复旋律4 Lv.4
1445 后缀自动机二·重复旋律5 Lv.4
1449 后缀自动机三·重复旋律6 Lv.4
1457 后缀自动机四·重复旋律7 Lv.1
1465 后缀自动机五·重复旋律8 Lv.1
1466 后缀自动机六·重复旋律9 Lv.1

后缀数组

思路简单但是实现要想一想?之前我看的是什么lj教程,不如自己xjb强行写一下递归形式的然后改成循环就好了(我自己写的跑得贼慢,什么时候看看别人咋改进的)

关于height数组,它的性质是显然的就不讲了,不过height数组给人的启示是,带有前缀交性质的查询可以将元素按照字典序排序,这样连续一段的前缀交=\((l,R]\)的相邻前缀交了。同时也有\(h[i]\ge h[i-1]-1\)这个结论。

重复旋律1

题目大意是问你满足这个条件的子串的最长长度

条件:在母串出现次数至少为k次(可以重叠出现)

子串=后缀的前缀,现在只要定位height数组任意一个长度为k-1的子段,查询一下其中的最小值,我们是要求这些最小值的最大值。线段树就行了

//@winlere
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define mid ((l+r)>>1)
#define lef l,mid,pos<<1
#define rgt mid+1,r,pos<<1|1
#define DEBUG(s,a) cerr<<#s" = "<<(s)<<" \n"[(a)==1]

using namespace std;  typedef long long ll;   char __buf[1<<18],*__c=__buf,*__ed=__buf;
inline int qr(){
    int ret=0,f=0,c=getchar();
    while(!isdigit(c))f|=c==45,c=getchar();
    while(isdigit(c)) ret=ret*10+c-48,c=getchar();
    return f?-ret:ret;
}
const int maxn=1e5+5;
char c[maxn];
int sa[maxn],h[maxn],rk[maxn],seg[maxn<<2],n;

void sufsort(char*c,int*sa,int*rk,int*h,int n){
    static int temp[maxn<<1],b[maxn];
    c[0]='!';
    for(int k=0,m=128;(1<<k>>1)<=n;++k){
        if(!k) for(int t=1;t<=n;++t) temp[t]=t,rk[t]=c[t],temp[t+n]=0;
        int l=1<<k>>1,p=0,q=l;
        for(int t=1;t<=n;++t){
            if(sa[t]>=n-l+1) temp[++p]=sa[t];
            if(sa[t]>l) temp[++q]=sa[t]-l;
        }
        for(int t=1;t<=n;++t) ++b[rk[t]];
        for(int t=1;t<=m;++t) b[t]+=b[t-1];
        for(int t=n;t;--t) sa[b[rk[temp[t]]]--]=temp[t];
        memset(b,0,(m+1)<<2); memcpy(temp+1,rk+1,n<<2); rk[sa[1]]=1;
        for(int t=2;t<=n;++t)
            rk[sa[t]]=temp[sa[t]]==temp[sa[t-1]]&&temp[sa[t]+l]==temp[sa[t-1]+l]?rk[sa[t-1]]:rk[sa[t-1]]+1;
        m=rk[sa[n]];
    }
    for(int t=1,l=0;t<=n;++t){
        if(l) --l;
        if(rk[t]>1) while(c[t+l]==c[sa[rk[t]-1]+l]) ++l;
        else l=0;
        h[rk[t]]=l;
    }
}

void build(int l,int r,int pos){
    if(l==r) return seg[pos]=h[l],void();
    build(lef); build(rgt);
    seg[pos]=min(seg[pos<<1],seg[pos<<1|1]);
}

int que(int L,int R,int l,int r,int pos){
    if(L>r||R<l) return 1e9;
    if(L<=l&&r<=R) return seg[pos];
    return min(que(L,R,lef),que(L,R,rgt));
}

int que(int l,int r){
    if(rk[l]>rk[r]) swap(l,r);
    return que(rk[l]+1,rk[r],1,n,1);
}

int main(){
    scanf("%s",c+1); n=strlen(c+1);
    sufsort(c,sa,rk,h,n);
    build(1,n,1);
    int ans=0;
    for(int t=1;t<=n;++t)
        for(int i=1;i<=n-t;i+=t){
            int k=que(i,i+t);
            ans=max(ans,k/t+1);
            if(i>=t-k%t) ans=max(que(i-t+k%t,i+k%t)/t+1,ans);
        }
    printf("%d\n",ans);
    return 0;
}

重复旋律2

题目大意是问你满足这个条件的子串的最长长度

条件:在母串出现次数至少为k次(不可以重叠出现)

现在问题其实就变成了:
\[ \max l \text{ 使得} \\ \exist i,j \text{ 满足 } i<j-l \and \text{lcp}(i,j)\ge l \]
显然满足二分性,二分就行。

//@winlere
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
#define DEBUG(s,a) cerr<<#s" = "<<(s)<<" \n"[(a)==1]
#define mid ((l+r)>>1)

using namespace std;  typedef long long ll;
inline int qr(){
    int ret=0,f=0,c=getchar();
    while(!isdigit(c))f|=c==45,c=getchar();
    while(isdigit(c)) ret=ret*10+c-48,c=getchar();
    return f?-ret:ret;
}
const int maxn=1e5+5;
int rk[maxn],sa[maxn],c[maxn],height[maxn],n;

inline void sufsort(int*str,int*sa,int*rk,int*height,int n){
    static int temp[maxn<<1],buk[maxn];
    str[0]=-1;
    for(int k=0,m=1000;(1<<k>>1)<=n;++k){
        if(!k) for(int t=1;t<=n;++t) temp[t]=t,temp[t+n]=0,rk[t]=str[t];
        int l=1<<k>>1,p=0,q=l;
        for(int t=1;t<=n;++t){
            if(sa[t]>=n-l+1) temp[++p]=sa[t];
            if(sa[t]>l) temp[++q]=sa[t]-l;
        }
        for(int t=1;t<=n;++t) ++buk[rk[t]];
        for(int t=1;t<=m;++t) buk[t]+=buk[t-1];
        for(int t=n;t;--t) sa[buk[rk[temp[t]]]--]=temp[t];
        memset(buk,0,(m+1)<<2); memcpy(temp+1,rk+1,n<<2); rk[sa[1]]=1;
        for(int t=1;t<=n;++t)
            rk[sa[t]]=temp[sa[t]]==temp[sa[t-1]]&&temp[sa[t]+l]==temp[sa[t-1]+l]?rk[sa[t-1]]:rk[sa[t-1]]+1;
        m=rk[sa[n]];
    }
    for(int t=1,l=0;t<=n;++t){
        if(l) --l;
        if(rk[t]>1) while(str[t+l]==str[sa[rk[t]-1]+l]) ++l;
        else l=0;
        height[rk[t]]=l;
    }
}

bool chek(int k){
    for(int l=1,r=1;r<=n;l=++r){
        if(height[l]<k) continue; 
        int Min=sa[l],Max=sa[l];
        if(l>1) Min=min(sa[l-1],Min),Max=max(Max,sa[l-1]);
        while(r<n&&height[r+1]>=k) ++r,Min=min(Min,sa[r]),Max=max(Max,sa[r]);
        if(Max-Min+1>k) return 1;
    }
    return 0;
}

int main(){
#ifndef ONLINE_JUDGE
    freopen("in.in","r",stdin);
    freopen("out.out","w",stdout);
#endif
    n=qr();
    for(int t=1;t<=n;++t) c[t]=qr();
    sufsort(c,sa,rk,height,n);
    int l=0,r=n;
    do
        if(chek(mid)) l=mid+1;
        else r=mid-1;
    while(l<=r);
    printf("%d\n",r);   
    return 0;
}

重复旋律3

问两个串的最长公共子串。

按道理应该可以AC自动机做,但是我没想出来

把两个串顺序连接在一起,中间设放一个分隔符位置设为k,问题就变成了
\[ \max l \text{ 使得} \\ \exist i,j \text{ 满足 } i<k\and j>k \and \text{lcp}(i,j)\ge l \]
显然满足二分性,直接二分就行

//@winlere
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define mid ((l+r)>>1)
#define DEBUG(s,a) cerr<<#s" = "<<(s)<<" \n"[(a)==1]

using namespace std;  typedef long long ll;   char __buf[1<<18],*__c=__buf,*__ed=__buf;
inline int qr(){
    int ret=0,f=0,c=getchar();
    while(!isdigit(c))f|=c==45,c=getchar();
    while(isdigit(c)) ret=ret*10+c-48,c=getchar();
    return f?-ret:ret;
}
const int maxn=2e5+5;
int sa[maxn],rk[maxn],height[maxn],n,k;
char c[maxn];

void sufsort(char*str,int*sa,int*rk,int*height,int n){
    static int temp[maxn<<1],buk[maxn];
    str[0]='~';
    for(int k=0,m=1000;(1<<k>>1)<=n;++k){
        if(!k) for(int t=1;t<=n;++t) temp[t]=t,temp[t+n]=0,rk[t]=str[t];
        int l=1<<k>>1,p=0,q=l;
        for(int t=1;t<=n;++t){
            if(sa[t]>=n-l+1) temp[++p]=sa[t];
            if(sa[t]>l) temp[++q]=sa[t]-l;
        }
        for(int t=1;t<=n;++t) ++buk[rk[t]];
        for(int t=1;t<=m;++t) buk[t]+=buk[t-1];
        for(int t=n;t;--t) sa[buk[rk[temp[t]]]--]=temp[t];
        memset(buk,0,(m+1)<<2); memcpy(temp+1,rk+1,n<<2); rk[sa[1]]=1;
        for(int t=2;t<=n;++t)
            rk[sa[t]]=temp[sa[t]]==temp[sa[t-1]]&&temp[sa[t]+l]==temp[sa[t-1]+l]?rk[sa[t-1]]:rk[sa[t-1]]+1;
        m=rk[sa[n]];
    }
    for(int t=1,l=0;t<=n;++t){
        if(l) --l;
        if(rk[t]>1) while(str[t+l]==str[sa[rk[t]-1]+l]) ++l;
        else l=0;
        height[rk[t]]=l;
    }
}


bool chek(int L){
    for(int l=1,r=1;r<=n;l=++r){
        if(height[l]<L) continue; 
        int Min=sa[l],Max=sa[l];
        if(l>1) Min=min(sa[l-1],Min),Max=max(Max,sa[l-1]);
        while(r<n&&height[r+1]>=L) ++r,Min=min(Min,sa[r]),Max=max(Max,sa[r]);
        if(Min<k&&Max>k) return 1;
    }
    return 0;
}

int main(){
    scanf("%s",c+1);
    n=strlen(c+1); c[k=++n]='_';
    scanf("%s",c+n);
    n=strlen(c+1);
    sufsort(c,sa,rk,height,n);
    int l=0,r=n;
    do
        if(chek(mid)) l=mid+1;
        else r=mid-1;
    while(l<=r);
    printf("%d\n",r);
    return 0;
}

重复旋律4

题意是问你,母串的某个子段可以被表示成k个相同的串重复k次,给你母串问你最大可能的k

用别的方法描述一下这个问题,就变成了要你在母串中找到一个位置的数列\(P=\{p_i\}\)满足\(P\)是一个等差数列且要求任意两个\(p\)的\(\text{lcp} \ge d\) ,答案是最大的\(|P|\)。

序列上的等差子序列问题一般和一个复杂度是调和级数\((O(1))\)的经典做法有关...

下午再更

//@winlere
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define mid ((l+r)>>1)
#define lef l,mid,pos<<1
#define rgt mid+1,r,pos<<1|1
#define DEBUG(s,a) cerr<<#s" = "<<(s)<<" \n"[(a)==1]

using namespace std;  typedef long long ll;   char __buf[1<<18],*__c=__buf,*__ed=__buf;
inline int qr(){
    int ret=0,f=0,c=getchar();
    while(!isdigit(c))f|=c==45,c=getchar();
    while(isdigit(c)) ret=ret*10+c-48,c=getchar();
    return f?-ret:ret;
}
const int maxn=1e5+5;
char c[maxn];
int sa[maxn],h[maxn],rk[maxn],seg[maxn<<2],n;

void sufsort(char*c,int*sa,int*rk,int*h,int n){
    static int temp[maxn<<1],b[maxn];
    c[0]='!';
    for(int k=0,m=128;(1<<k>>1)<=n;++k){
        if(!k) for(int t=1;t<=n;++t) temp[t]=t,rk[t]=c[t],temp[t+n]=0;
        int l=1<<k>>1,p=0,q=l;
        for(int t=1;t<=n;++t){
            if(sa[t]>=n-l+1) temp[++p]=sa[t];
            if(sa[t]>l) temp[++q]=sa[t]-l;
        }
        for(int t=1;t<=n;++t) ++b[rk[t]];
        for(int t=1;t<=m;++t) b[t]+=b[t-1];
        for(int t=n;t;--t) sa[b[rk[temp[t]]]--]=temp[t];
        memset(b,0,(m+1)<<2); memcpy(temp+1,rk+1,n<<2); rk[sa[1]]=1;
        for(int t=2;t<=n;++t)
            rk[sa[t]]=temp[sa[t]]==temp[sa[t-1]]&&temp[sa[t]+l]==temp[sa[t-1]+l]?rk[sa[t-1]]:rk[sa[t-1]]+1;
        m=rk[sa[n]];
    }
    for(int t=1,l=0;t<=n;++t){
        if(l) --l;
        if(rk[t]>1) while(c[t+l]==c[sa[rk[t]-1]+l]) ++l;
        else l=0;
        h[rk[t]]=l;
    }
}

void build(int l,int r,int pos){
    if(l==r) return seg[pos]=h[l],void();
    build(lef); build(rgt);
    seg[pos]=min(seg[pos<<1],seg[pos<<1|1]);
}

int que(int L,int R,int l,int r,int pos){
    if(L>r||R<l) return 1e9;
    if(L<=l&&r<=R) return seg[pos];
    return min(que(L,R,lef),que(L,R,rgt));
}

int que(int l,int r){
    if(rk[l]>rk[r]) swap(l,r);
    return que(rk[l]+1,rk[r],1,n,1);
}

int main(){
    scanf("%s",c+1); n=strlen(c+1);
    sufsort(c,sa,rk,h,n);
    build(1,n,1);
    int ans=0;
    for(int t=1;t<=n;++t)
        for(int i=1;i<=n-t;i+=t){
            int k=que(i,i+t);
            ans=max(ans,k/t+1);
            if(i>=t-k%t) ans=max(que(i-t+k%t,i+k%t)/t+1,ans);
        }
    printf("%d\n",ans);
    return 0;
}

标签:简要,return,int,题解,Hihocoder,++,sa,include,rk
来源: https://www.cnblogs.com/winlere/p/12114519.html