[bzoj4698][SDOI2008]Sandy的卡片

本文介绍了一个涉及卡片收集的游戏问题解决方案,通过将问题转化为求最长公共子串的问题,并使用广义后缀树和线段树合并的方法高效求解,旨在帮助玩家确定能兑换的人物模型等级。

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

题目描述

Sandy和Sue的热衷于收集干脆面中的卡片。然而,Sue收集卡片是因为卡片上漂亮的人物形象,而Sandy则是为了积
攒卡片兑换超炫的人物模型。每一张卡片都由一些数字进行标记,第i张卡片的序列长度为Mi,要想兑换人物模型
,首先必须要集够N张卡片,对于这N张卡片,如果他们都有一个相同的子串长度为k,则可以兑换一个等级为k的人
物模型。相同的定义为:两个子串长度相同且一个串的全部元素加上一个数就会变成另一个串。Sandy的卡片数远
远小于要求的N,于是Sue决定在Sandy的生日将自己的卡片送给Sandy,在Sue的帮助下,Sandy终于集够了N张卡片
,但是,Sandy并不清楚他可以兑换到哪个等级的人物模型,现在,请你帮助Sandy和Sue,看看他们最高能够得到
哪个等级的人物模型。

广义后缀树

我们可以知道两个子串如果相同
那么剔除第一个元素和的差分序列应完全一致
因此可以把每个字符串变成剔除第一个元素后的差分序列。
模型转化为求所有串的最长公共子串长度+1。
可以对所有串建广义后缀树(不懂看我blog,大概就是trie上建sam的产物),接下来用线段树合并来得到一个点所代表的字符串集合在几个串中出现过。那我们在所有n个串里都出现过的状态找个max最大的作为答案。

#include<cstdio>
#include<algorithm>
#include<map>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
const int maxn=110000+10,maxtot=2200000+10;
map<int,int> g[maxn*2];
int root[maxn*2],left[maxtot],right[maxtot],size[maxtot],fail[maxn*2],step[maxn*2];
int a[maxn*2],b[maxn*2];
int s[110];
int i,j,k,l,t,n,m,tot,top,last,mx,ans;
int read(){
    int x=0,f=1;
    char ch=getchar();
    while (ch<'0'||ch>'9'){
        if (ch=='-') f=-1;
        ch=getchar();
    }
    while (ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
void insert(int &x,int l,int r,int a){
    if (!x) x=++top;
    if (l==r){
        size[x]=1;
        return;
    }
    int mid=(l+r)/2;
    if (a<=mid) insert(left[x],l,mid,a);else insert(right[x],mid+1,r,a);
    size[x]=size[left[x]]+size[right[x]];
}
int merge(int a,int b,int l,int r){
    if (!a||!b) return a+b;
    if (l==r){
        size[a]=min(size[a]+size[b],1);
        return a;
    }
    int mid=(l+r)/2;
    left[a]=merge(left[a],left[b],l,mid);
    right[a]=merge(right[a],right[b],mid+1,r);
    size[a]=size[left[a]]+size[right[a]];
    return a;
}
void add(int x){
    int p=last;
    if (g[p][x]){
        int q=g[p][x];
        if (step[q]==step[p]+1) last=q;
        else{
            int nq=++tot;
            step[nq]=step[p]+1;
            fail[nq]=fail[q];
            fail[q]=nq;
            g[nq]=g[q];
            while (p&&g[p][x]==q){
                g[p][x]=nq;
                p=fail[p];
            }
            last=nq;
        }
        insert(root[last],1,n,i);
        return;
    }
    int np=++tot;
    step[np]=step[p]+1;
    while (p&&g[p][x]==0){
        g[p][x]=np;
        p=fail[p];
    }
    if (!p) fail[np]=1;
    else{
        int q=g[p][x];
        if (step[q]==step[p]+1) fail[np]=q;
        else{
            int nq=++tot;
            fail[np]=nq;
            step[nq]=step[p]+1;
            fail[nq]=fail[q];
            fail[q]=nq;
            g[nq]=g[q];
            while (p&&g[p][x]==q){
                g[p][x]=nq;
                p=fail[p];
            }
        }
    }
    last=np;
    insert(root[last],1,n,i);
}
int main(){
    tot=1;
    n=read();
    fo(i,1,n){
        m=read();
        fo(j,1,m) s[j]=read();
        fd(j,m,2) s[j]-=s[j-1];
        s[1]=0;
        last=1;
        fo(j,2,m) add(s[j]);
    }
    mx=0;
    fo(i,1,tot)
        if (mx<step[i]) mx=step[i];
    fo(i,1,tot) b[step[i]]++;
    fo(i,1,mx) b[i]+=b[i-1];
    fd(i,tot,1) a[b[step[i]]--]=i;
    ans=0;
    fd(i,tot,2){
        if (size[root[a[i]]]==n) ans=max(ans,step[a[i]]);
        root[fail[a[i]]]=merge(root[fail[a[i]]],root[a[i]],1,n);
    }
    printf("%d\n",ans+1);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值