BJ 集训测试11 level&& codeforces 700E

博客介绍了如何解决一个字符串等级计算问题,其中涉及到使用SAM(suffix automaton)和可持久化线段树的数据结构。通过暴力DP和哈希的方法可以达到O(n^3)的时间复杂度,但存在更优的解决方案。博主提到一位大佬使用哈希可以在O(能过)的时间复杂度内解决,而博主则分享了利用SAM和可持久化线段树将问题转化为递归结构,通过判断字符串是否存在重复的最高等级子串来确定最终等级。这种方法的关键在于在线段树中查找重复子串并更新答案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

http://www.elijahqi.win/archives/2796
题意:给定一串字符求其等级 对于等级的定义是

字符串中出现至少两次的等级最高的字符串的等级+1

那么有个显然的n^3 暴力dp+hash的做法设dp[i][j]表示i开始长度为j的这段区间的等级是多少 每次直接比较前缀后缀 即可 据说gzez某大佬有hash 的做法可以ac 复杂度o(能过) 我不会..

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 5010
#define mod 1004535809
#define ll unsigned long long
#define g1 131
#define g2 27
using namespace std;
char s[N];ll p1[N],hs1[N];
int dp[N][N];int p2[N],hs2[N];
inline ll calc1(int l,int r){
    return hs1[r]-hs1[l-1]*p1[r-l+1];
}
inline int calc2(int l,int r){
    return ((ll)hs2[r]-(ll)hs2[l-1]*p2[r-l+1]%mod+mod)%mod;
}
inline bool judge(int l,int r){
    bool flag1=1,flag2=1;
    flag1&=calc1(l,r-1)==calc1(l+1,r);
    flag2&=calc2(l,r-1)==calc2(l+1,r);
    return flag1&&flag2;
}
inline bool check(int l1,int r1,int l2,int r2){
    bool flag1=1,flag2=1;
    flag1&=(calc1(l1,r1)==calc1(l2,r2));
    flag2&=(calc2(l1,r1)==calc2(l2,r2));
    return flag1&&flag2;
}
int main(){
//  freopen("t1.in","r",stdin);
    scanf("%s",s+1);int n=strlen(s+1);p1[0]=1;p2[0]=1;
    for (int i=1;i<=n;++i) dp[i][1]=1,p1[i]=p1[i-1]*g1,p2[i]=(ll)p2[i-1]*g2%mod,
    hs1[i]=hs1[i-1]*g1+s[i]-'a'+1,hs2[i]=((ll)hs2[i-1]*g2+s[i]-'a'+1)%mod;
    for (int j=2;j<=n;++j)
        for (int i=1;i+j<=n+1;++i){int mx=-1;
            for (int k=1;k<=j-1;++k){
                if (check(i,i+k-1,i+j-k,i+j-1)) mx=max(mx,max(dp[i][k],dp[i+j-k][k]));
            }if (mx==-1) dp[i][j]=max(dp[i][j-1],dp[i+1][j-1]);
            else dp[i][j]=max(mx+1, max(dp[i][j-1],dp[i+1][j-1]));
        }
    printf("%d\n",dp[1][n]);
    return 0;
}

SAM+可持久化线段树 做法:

假如有一个k级的东西 那么显然他可以由两个k-1的前缀和后缀组成 同理两个k-1的前缀和后缀也可以由k-2的后缀等 组成

所以我们可以先针对整个串建立sam 然后在par树上倒着从下往上推 每次把合并两个节点的right集合 考虑一个字符串A 如果开头和结尾都是比他低一个等级的字符串B 那么显然B 不会和A在同一个节点 因为right集合不相同 那么结论显然 所以只需要判断我这个串包含的最大的等级的那个 串有没有出现两次如果有就 给我自己的答案+1反之 我的答案就等于那个串的答案 这部分建可持久化线段树即可 因为是出现两次 所以每次去找一找有没有这个点即可


#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 200010
using namespace std;
inline char gc(){
    static char now[1<<16],*S,*T;
    if (T==S){T=(S=now)+fread(now,1,1<<16,stdin);if (T==S) return EOF;}
    return *S++;
}
int len[N<<1],fa[N<<1],cnt=1,c[N],last=1,root=1,id[N<<1],ch[N<<1][26],rk[N<<1],rt[N<<1],dp[N<<1],dp1[N<<1];
inline void insert1(int x,int idx){
    int np=++cnt,p=last;len[np]=len[p]+1;fa[np]=p;id[np]=idx;
    for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np;
    if (!p) fa[np]=root;else {
        int q=ch[p][x];if (len[p]+1==len[q]) fa[np]=q;else{
            int nq=++cnt;memcpy(ch[nq],ch[q],sizeof(ch[q]));fa[nq]=fa[q];fa[q]=fa[np]=nq;
            id[nq]=idx;len[nq]=len[p]+1;for (;p&&ch[p][x]==q;p=fa[p]) ch[p][x]=nq;
        }
    }last=np;
}
struct node{int left,right;}tree[N*40];
inline void insert2(int &x,int l,int r,int p){
    if (!x) x=++cnt;if (l==r) return;int mid=l+r>>1;
    if (p<=mid) insert2(tree[x].left,l,mid,p);else insert2(tree[x].right,mid+1,r,p);
}
inline int merge(int p1,int p2){
    if (!p1||!p2) return p1+p2;int owo=++cnt;
    tree[owo].left=merge(tree[p1].left,tree[p2].left);
    tree[owo].right=merge(tree[p1].right,tree[p2].right);
    return owo;
}
inline bool query(int x,int l,int r,int l1,int r1){
    if (!x) return 0;if (l1<=l&&r1>=r) return 1;int mid=l+r>>1;
    if (l1<=mid&&query(tree[x].left,l,mid,l1,r1)) return 1;
    if (r1>mid&&query(tree[x].right,mid+1,r,l1,r1)) return 1;return 0;
}
int main(){
    freopen("t1.in","r",stdin);
    char ch=gc();int n=0;while(ch<'a'||ch>'z') ch=gc();
    while(ch>='a'&&ch<='z') ++n,insert1(ch-'a',n),ch=gc();
    for (int i=1;i<=cnt;++i) ++c[len[i]];int nn=cnt;
    for (int i=1;i<=n;++i) c[i]+=c[i-1];cnt=0;
    for (int i=nn;i;--i) rk[c[len[i]]--]=i;
    for (int i=nn;i;--i){int x=rk[i];
        insert2(rt[x],1,n,id[x]);rt[fa[x]]=merge(rt[fa[x]],rt[x]);
    }int ans=1;
    for (int i=2;i<=nn;++i){int x=rk[i];
        if (fa[x]==root) {dp[x]=1,dp1[x]=x;continue;}
        if (query(rt[dp1[fa[x]]],1,n,id[x]-len[x]+len[dp1[fa[x]]],id[x]-1)) dp[x]=dp[fa[x]]+1,dp1[x]=x,ans=max(ans,dp[x]);
        else dp[x]=dp[fa[x]],dp1[x]=dp1[fa[x]];
    }printf("%d\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值