[NOI2018]你的名字

苍天啊,大地啊,模拟赛里怎么全是毒瘤题啊!

题目链接:luoguP4770

这道题让我们求的是 $ t $ 所有本质不同子串中不为 $ S[l,r] $ 的子串的个数。

首先我们可以利用 SAM 非常简单的求出 $ t $ 的本质不同子串个数。

什么?你不知道怎么求,建议先做luoguP3804 【模板】后缀自动机 (SAM)

下面我们只需要对每一个询问求出包含在 $ S[l,r] $ 中的 $ t $ 的所有本质不同子串个数就可以了。

68分做法

我们可以看到,整道题前面 $ 68 $ 分都有 $ l=1,r=|S| $ ,我们不妨先来思考这些测试点怎么做。

如果我们想要知道 $ t $ 的哪些子串同时也是 $ S $ 的子串,最好的方法肯定就是对 $ |S| $ 建立一个 SAM,然后用 $ t $ 在上面匹配。

因为SAM的fail的含义于AC自动机的差不多,指向的都是该节点代表字符串的后缀。

并且SAM中包含一个字符串的所有子串,就相当于是把一个字符串的所有子串抽出来建了一个AC自动机。

所以实际上是可以进行匹配的(可能还挺常用?)。

这样子我们就可以知道对于 $ t $ 的每一个前缀 $ t[1,i] $ ,它的最长后缀的长度使得这个后缀是 $ S $ 的子串。

但是这样找出的子串并不能保证本质不同。

那么我们把这个过程搬到 SAM 上面。

我们再对 $ t $ 建一个 SAM,并且对于每一个点 $ i $ 再新维护一个信息 $ val_i $ ,表示第 $ i $ 个点表示的所有字符串中,长度小于等于 $ val_i $ 的都是 $ S $ 的子串。

我们只需要把 $ t $ 在 $ S $ 的 SAM 上面跑一遍,然后顺着 parent 边更新答案就可以了(拓扑排序或者dfs都行)。

其实上面找 $ val_i $ 的过程事实上就是这道题,可以做一下加深理解。

然后我们就可以拿到 $ 68 $ 分了!

满分做法

我们发现,上面的做法不能AC主要原因在于每一次询问的是 $ S $ 的一个子串,我们使用 $ t $ 进行匹配的时候很有可能“越界”,而我们又不能快速得到 $ S $ 的某个子串的SAM。

我们无法快速得到一个子串的SAM,那我们能否快速查询某一个节点代表的某个子串是否在 $ [l,r] $ 范围内呢?

我们知道,SAM上某一个节点代表字符串的位置实际上是和它的 endpos 有关的,那么我们能不能快速得到某个节点的 endpos 集合呢?

答案是可以的,我们需要在SAM的parent上面跑线段树合并。

所以我们只需要在沿边转移的时候加上一个endpos的条件即可。

停停,线段树合并不是离线的吗?

所以我们需要进行可持久化线段树合并。

和普通线段树合并不同的一点是,我们每次合并两个节点时新建一个节点,这样就不会破坏原来的节点,并且在所有查询之前进行合并,这样时间复杂度不会变,算法也变成了在线。

然后快乐提交,拿到 $ 97 $ 分,挂了一个点。

代码

提交记录

哪里挂了呢?

你会发现假如某一个节点因为endpos而不能转移时,你直接跳了fail,但是有可能在匹配长度减小一些以后它又可以转移了。

稍微转移一下就可以过。

代码:

#include<cstring>
#include<cstdio>
#include<vector>
#include<queue>
using namespace std;
template<typename A>
using vc=vector<A>;
using pi=pair<int,int>;
using ll=long long;
inline int read()
{
    int s=0,w=1;char ch;
    while((ch=getchar())>'9'||ch<'0') if(ch=='-') w=-1;
    while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
    return s*w;
}
int root[1000001];
vc<int>g[1000001];
int ls[30000001];
int rs[30000001];
int son[2000001][26];
int fail[2000001];
int len[2000001];
vc<int>t[100001];
vc<int>w[100001];
char in[1000002];
int mo[2000001];
int ma[2000001];
char s[500002];
ll ans[100001];
int l[100001];
int r[100001];
int cnt=1;
int lst=1;
int tot;
int n,m;
void clear()
{
    for(int i=1;i<=cnt;i++)
    {
        fail[i]=len[i]=ma[i]=0;
        for(int j=0;j<26;j++) son[i][j]=0;
    }
    cnt=lst=1;
}
void add(int a)
{
    int cur=++cnt,p=lst;lst=cur,len[cur]=len[p]+1;
    for(;p&&!son[p][a];p=fail[p]) son[p][a]=cur;
    if(!p){ fail[cur]=1;return ;}
    int q=son[p][a];
    if(len[q]==len[p]+1){ fail[cur]=q;return ;}
    int nq=++cnt;len[nq]=len[p]+1;
    fail[nq]=fail[q],fail[q]=fail[cur]=nq;
    for(int i=0;i<26;i++) son[nq][i]=son[q][i];
    for(;p&&son[p][a]==q;p=fail[p]) son[p][a]=nq;
}
void debug()
{
    // for(int i=1;i<=cnt;i++)
    // {
    //     printf("%d : len=%d fail=%d ",i,len[i],fail[i]);
    //     for(int j=0;j<26;j++) if(son[i][j]) printf("(%c,%d) ",j+'a',son[i][j]);
    //     printf("\n");
    // }
}
void add(int &p,int pl,int pr,int x)
{
    if(!p) p=++tot;
    if(pl==pr) return ;
    int mid=(pl+pr)>>1;
    if(x<=mid) add(ls[p],pl,mid,x);
    else add(rs[p],mid+1,pr,x);
}
int merge(int u,int v,int pl,int pr)
{
    if(!u||!v) return u|v;
    int ans=++tot;
    if(pl==pr) return ans;
    int mid=(pl+pr)>>1;
    ls[ans]=merge(ls[u],ls[v],pl,mid);
    rs[ans]=merge(rs[u],rs[v],mid+1,pr);
    return ans;
}
void dfs(int num)
{
    for(int p:g[num]) dfs(p),root[num]=merge(root[num],root[p],1,m);
}
void topo()
{
    queue<int>que;
    for(int i=2;i<=cnt;i++) mo[fail[i]]++;
    for(int i=1;i<=cnt;i++) if(!mo[i]) que.push(i);
    while(!que.empty())
    {
        int p=que.front();que.pop();
        ma[fail[p]]=max(ma[fail[p]],ma[p]),mo[fail[p]]--;
        if(!mo[fail[p]]) que.push(fail[p]);
    }
}
bool get(int p,int pl,int pr,int l,int r)
{
    // printf("get %d %d %d %d %d\n",p,pl,pr,l,r);
    if(!p) return false;
    if(l<=pl&&pr<=r) return true;
    int mid=(pl+pr)>>1;
    if(l<=mid&&get(ls[p],pl,mid,l,r)) return true;
    if(mid<r&&get(rs[p],mid+1,pr,l,r)) return true;
    return false;
}
int main()
{
    // freopen("name.in","r",stdin);
    // freopen("name.out","w",stdout);
    scanf("%s",s+1),n=read(),m=strlen(s+1);
    for(int i=1;s[i];i++) add(s[i]-'a');
    for(int i=1,now=1;s[i];i++) now=son[now][s[i]-'a'],add(root[now],1,m,i);
    for(int i=2;i<=cnt;i++) g[fail[i]].push_back(i);
    dfs(1),debug();
    // for(int i=1;i<=cnt;i++) printf("%d ",root[i]);
    // printf("\n");
    for(int i=1;i<=n;i++)
    {
        scanf("%s",in+1);int l=read(),r=read(),now=1,le=0;
        for(int j=1;in[j];j++)
        {
            t[i].push_back(in[j]-'a');
            while(true)
            {
                if(son[now][in[j]-'a']&&le<=r-l&&get(root[son[now][in[j]-'a']],1,m,l+le,r))
                {
                    now=son[now][in[j]-'a'],le++;
                    break;
                }
                if(!le) break;
                // printf("le : %d -> %d\n",le,le-1);
                le--;
                if(len[fail[now]]==le) now=fail[now];
            }
            w[i].push_back(le);
            // printf("j=%d : now=%d le=%d\n",j,now,le);
            // printf("%d ",le);
        }
        // printf("\n");
    }
    for(int i=1;i<=n;i++)
    {
        clear();
        for(unsigned j=0;j<t[i].size();j++) add(t[i][j]);
        for(int j=0,now=1;j<(int)t[i].size();j++)
        {
            now=son[now][t[i][j]];
            ma[now]=max(ma[now],w[i][j]);
            // printf("%d : %d\n",now,w[i][j]);
        }
        topo();
        for(int j=2;j<=cnt;j++)
        {
            ans[i]-=max(0,min(len[j],ma[j])-len[fail[j]]);
            ans[i]+=len[j]-len[fail[j]];
        }
    }
    for(int i=1;i<=n;i++) printf("%lld\n",ans[i]);
    return 0;
}
/*
scbamgepe
1
sgepe 1 9
*/
posted @ 2022-06-23 16:08  Wuyanru  阅读(125)  评论(0编辑  收藏  举报