【简要题解】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;
}

原文地址:https://www.cnblogs.com/winlere/p/12114519.html

时间: 2024-10-28 22:06:02

【简要题解】Hihocoder 重复旋律1-8简要题解的相关文章

后缀数组之hihocoder 重复旋律1-4

蒟蒻知道今天才会打后缀数组,而且还是nlogn^2的...但基本上还是跑得过的: 重复旋律1: 二分答案,把height划分集合,height<mid就重新划分,这样保证了每个集合中的LCP>=mid,套路板子题 // MADE BY QT666 #include<cstdio> #include<algorithm> #include<cmath> #include<iostream> #include<cstring> using

hihocoder #1407 : 后缀数组二&#183;重复旋律2

#1407 : 后缀数组二·重复旋律2 Time Limit:5000ms Case Time Limit:1000ms Memory Limit:256MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一个音乐旋律被表示为长度为 N 的数构成的数列.小Hi在练习过很多曲子以后发现很多作品自身包含一样的旋律. 旋律可以表示为一段连续的数列,相似的旋律在原数列不可重叠,比如在1 2 3 2 3 2 1 中 2 3 2 出现了一次,2 3 出现了两次,小Hi想知道一段旋律中出现次数至少为两次

hihocoder #1449 : 后缀自动机三&#183;重复旋律6

#1449 : 后缀自动机三·重复旋律6 时间限制:15000ms 单点时限:3000ms 内存限制:512MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一个音乐旋律被表示为一段数构成的数列. 现在小Hi想知道一部作品中所有长度为K的旋律中出现次数最多的旋律的出现次数.但是K不是固定的,小Hi想知道对于所有的K的答案. 解题方法提示 × 解题方法提示 小Hi:上次我们已经学习了后缀自动机了,今天我们再来解决一个用到后缀自动机的问题. 小Ho:好!那我们开始吧! 小Hi:现在我们要对K

hihocoder #1457 : 后缀自动机四&#183;重复旋律7

#1457 : 后缀自动机四·重复旋律7 时间限制:15000ms 单点时限:3000ms 内存限制:512MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一段音乐旋律可以被表示为一段数构成的数列. 神奇的是小Hi发现了一部名字叫<十进制进行曲大全>的作品集,顾名思义,这部作品集里有许多作品,但是所有的作品有一个共同特征:只用了十个音符,所有的音符都表示成0-9的数字. 现在小Hi想知道这部作品中所有不同的旋律的“和”(也就是把串看成数字,在十进制下的求和,允许有前导0).答案有可能

hihocoder #1465 : 后缀自动机五&#183;重复旋律8

#1465 : 后缀自动机五·重复旋律8 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一段音乐旋律可以被表示为一段数构成的数列. 小Hi发现旋律可以循环,每次把一段旋律里面最前面一个音换到最后面就成为了原旋律的“循环相似旋律”,还可以对“循环相似旋律”进行相同的变换能继续得到原串的“循环相似旋律”. 小Hi对此产生了浓厚的兴趣,他有若干段旋律,和一部音乐作品.对于每一段旋律,他想知道有多少在音乐作品中的子串(重复便多

hihocoder #1415 : 后缀数组三&#183;重复旋律3

#1415 : 后缀数组三·重复旋律3 Time Limit:5000ms Case Time Limit:1000ms Memory Limit:256MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一个音乐旋律被表示为长度为 N 的数构成的数列.小Hi在练习过很多曲子以后发现很多作品中的旋律有共同的部分. 旋律是一段连续的数列,如果同一段旋律在作品A和作品B中同时出现过,这段旋律就是A和B共同的部分,比如在abab 在 bababab 和 cabacababc 中都出现过.小Hi想

hihoCoder 后缀数组 重复旋律

#1403 : 后缀数组一·重复旋律 时间限制:5000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一个音乐旋律被表示为长度为 N 的数构成的数列. 小Hi在练习过很多曲子以后发现很多作品自身包含一样的旋律.旋律是一段连续的数列,相似的旋律在原数列可重叠.比如在1 2 3 2 3 2 1 中 2 3 2 出现了两次. 小Hi想知道一段旋律中出现次数至少为K次的旋律最长是多少? 解题方法提示 输入 第一行两个整数 N和K.1≤N≤2000

hihoCoder 后缀自动机三&#183;重复旋律6

后缀自动机三·重复旋律6 时间限制:15000ms 单点时限:3000ms 内存限制:512MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一个音乐旋律被表示为一段数构成的数列. 现在小Hi想知道一部作品中所有长度为K的旋律中出现次数最多的旋律的出现次数.但是K不是固定的,小Hi想知道对于所有的K的答案. 解题方法提示 输入 共一行,包含一个由小写字母构成的字符串S.字符串长度不超过 1000000. 输出 共Length(S)行,每行一个整数,表示答案. 样例输入 aab 样例输出

hihocoder 1419 重复旋律4

描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一个音乐旋律被表示为长度为 N 的数构成的数列.小Hi在练习过很多曲子以后发现很多作品中的旋律有重复的部分. 我们把一段旋律称为(k,l)-重复的,如果它满足由一个长度为l的字符串重复了k次组成. 如旋律abaabaabaaba是(4,3)重复的,因为它由aba重复4次组成. 小Hi想知道一部作品中k最大的(k,l)-重复旋律. 解题方法提示 输入 一行一个仅包含小写字母的字符串.字符串长度不超过 100000. 输出 一行一个整数,表示答案k