【[NOI2018]你的名字】

题目

可能是一个乱搞做法,同时也跪求有人能帮我分析一下复杂度

还是先来看比较简单的\(68pts\),也就是\(l=1,r=|S|\)的情况

我们可以直接把\(S\)串和所有的\(T\)串一起建一个广义\(SAM\),用一个\(vector\)维护每个\(T\)加入\(SAM\)时新产生的节点

我们只需要求出来这些新增节点没有在\(S\)串出现的本质不同的子串个数就好了

我们提前处理好每一个节点的\(endpos\),标记一下其是否在\(S\)中出现过

对于那些新出现在\(SAM\)上的节点\(x\)我们可以直接判断一下其是否在\(S\)中出现过,经典操作自然是\(ans+=len[x]-len[fa[x]]\)

但是考虑到\(x\)\(parent\)树上的祖先自然也在当前的\(T\)中出现过,于是我们还需要考虑这些节点的贡献

倍增?看起来好像非常可行,但是还有一个问题,就是判重

显然在处理一个\(T\)的时候\(parent\)树上的一个节点不能被计算两次,于是在\(parent\)树上倍增又不太可行了,因为不太方便我们打标记来判重

倍增不行我们就暴力啊,我们直接暴力访问\(x\)的祖先们,一旦有一个祖先在之前被访问过或者是在\(S\)中出现过,那么我们就不在往上跳了

至于复杂度我也不知道是什么,我甚至都觉得这个样子最坏会导致每次都把\(parent\)树遍历一遍,所以求有神仙能帮忙分析一下这个玄学的复杂度

\(68pts\)代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#define maxn 3000005
#define re register
#define LL long long
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
char S[maxn],T[maxn];
int n,m,l,r,cnt=1,lst=1,num;
struct E{int v,nxt;}e[maxn];
std::vector<int> v[100005];
int fa[maxn],len[maxn],endpos[maxn],son[maxn][26],head[maxn],vis[maxn];
int top,st[maxn];
inline void add(int x,int y) {e[++num].v=y;e[num].nxt=head[x];head[x]=num;}
inline void ins(int c,int o)
{
    int p=++cnt,f=lst; lst=p;
    len[p]=len[f]+1,endpos[p]=o;
    if(o>1) v[o-1].push_back(p);
    while(f&&!son[f][c]) son[f][c]=p,f=fa[f];
    if(!f) {fa[p]=1;return;}
    int x=son[f][c];
    if(len[f]+1==len[x]) {fa[p]=x;return;}
    int y=++cnt;
    if(o>1) v[o-1].push_back(y);
    len[y]=len[f]+1,fa[y]=fa[x],fa[x]=fa[p]=y;
    for(re int i=0;i<26;i++) son[y][i]=son[x][i];
    while(f&&son[f][c]==x) son[f][c]=y,f=fa[f];
}
void dfs(int x) {if(endpos[x]!=1) endpos[x]=0; for(re int i=head[x];i;i=e[i].nxt) dfs(e[i].v),endpos[x]|=endpos[e[i].v];}
int main()
{
    scanf("%s",S+1);n=strlen(S+1);
    for(re int i=1;i<=n;i++) ins(S[i]-'a',1);
    scanf("%d",&m);
    for(re int i=1;i<=m;i++)
    {
        scanf("%s",T+1),n=strlen(T+1);
        scanf("%d%d",&l,&r);
        lst=1;
        for(re int j=1;j<=n;j++) 
            ins(T[j]-'a',i+1);
    }
    for(re int i=2;i<=cnt;i++) add(fa[i],i); dfs(1);
    for(re int i=1;i<=m;i++)
    {
        LL ans=0;top=0;
        for(re int j=0;j<v[i].size();j++)
        {
            int t=v[i][j];
            if(endpos[t]||vis[t]) continue;
            ans+=len[t];st[++top]=t;vis[t]=1;
            while(!vis[fa[t]]&&fa[t]&&!endpos[fa[t]]) t=fa[t],vis[t]=1,st[++top]=t;
            ans-=len[fa[t]];
        }
        for(re int j=1;j<=top;j++) vis[st[j]]=0;
        printf("%lld\n",ans);
    }
    return 0;
}

再来看看剩下的非特殊情况

看到\(S\)串里的区间我们就知道我们不能只是粗略维护\(endpos\)集合了,我们有时候甚至得关心\(endpos\)里到底有哪些元素

于是我们得用一个可持久化数据结构来维护一下\(endpos\)集合

这里选择的是主席树,至于做法和上面差不多

我们还是对\(SAM\)上新增的节点以及其祖先算贡献,每次用主席树找出当前节点\(x\)\(endpos\)集合里小于等于\(r\)的最大值\(now\)

根据\(now\)\(l\)的关系进行讨论

  1. 如果\(now-len[fa[x]]>=l\),那么就说明当前这个节点表示的子串里已经有一些完全出现在了\([l,r]\)中,所以我们没有必要往上进行了,在这里计算贡献就好了,减掉那些完全出现在\([l,r]\)里的子串,也就是\(now-l+1\),但是可能这个节点根本产生不了这些子串,于是需要和\(len[x]\)取一个\(min\)

  2. 否则的话这个节点表示的子串里没有一个完全出现在\([l,r]\)中,所以还要继续算下去

但是这样每次都需要在主席树里二分,可以加一个小优化,一旦\(endpos\)集合的最大值小于\(l\),或者最小值大于\(r\),我们就不在主席树里二分了

这里的复杂度和上面相比多了一个\(log\),于是更加玄学了,并不保证代码能随时不T

交上去能获得取决于评测机稳定程度的分数的代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#define maxn 3000005
#define M 10000005
#define re register
#define LL long long
inline int min(int a,int b) {return (a<b)?a:b;}
inline int max(int a,int b) {return (a>b)?a:b;}
char S[500005],T[500005];
int n,m,cnt=1,lst=1,num,U,__,tot;
struct E{int v,nxt;}e[maxn];
std::vector<int> a[100005];
int x[100005],y[100005];
int fa[maxn],len[maxn],endpos[maxn],son[maxn][26],head[maxn];
int rt[maxn],sz[maxn],to[maxn],_to[maxn],mx[maxn],tx[maxn];
unsigned short vis[maxn];
int top,st[500005];
int l[M],r[M],d[M];
inline void add(int x,int y) {e[++num].v=y;e[num].nxt=head[x];head[x]=num;}
inline void ins(int c,int pos,int o)
{
    int p=++cnt,f=lst; lst=p;
    len[p]=len[f]+1,endpos[p]=tx[p]=mx[p]=pos;
    if(o) a[o].push_back(p);
    while(f&&!son[f][c]) son[f][c]=p,f=fa[f];
    if(!f) {fa[p]=1;return;}
    int x=son[f][c];
    if(len[f]+1==len[x]) {fa[p]=x;return;}
    int y=++cnt;
    len[y]=len[f]+1,fa[y]=fa[x],fa[x]=fa[p]=y;
    for(re int i=0;i<26;i++) son[y][i]=son[x][i];
    while(f&&son[f][c]==x) son[f][c]=y,f=fa[f];
}
void dfs(int x)
{
    to[x]=++__;_to[__]=x;sz[x]=1;
    if(!tx[x]) tx[x]=U+1;
    for(re int i=head[x];i;i=e[i].nxt) 
    {
        int v=e[i].v;
        dfs(v),sz[x]+=sz[v];
        mx[x]=max(mx[v],mx[x]);
        tx[x]=min(tx[x],tx[v]);
    }
}
int change(int pre,int x,int y,int pos)
{
    int root=++tot;
    d[root]=d[pre]+1;
    if(x==y) return root;
    l[root]=l[pre],r[root]=r[pre];
    int mid=x+y>>1;
    if(pos<=mid) l[root]=change(l[pre],x,mid,pos);
        else r[root]=change(r[pre],mid+1,y,pos);
    return root;
}
int query(int p1,int p2,int x,int y,int pos)
{
    if(x==y) return d[p2]-d[p1];
    int mid=x+y>>1;
    if(pos<=mid) return query(l[p1],l[p2],x,mid,pos);
    return d[l[p2]]-d[l[p1]]+query(r[p1],r[p2],mid+1,y,pos);
}
int ask(int p1,int p2,int x,int y,int k)
{
    if(x==y) return x;
    int now=d[l[p2]]-d[l[p1]];
    int mid=x+y>>1;
    if(k>now) return ask(r[p1],r[p2],mid+1,y,k-now);
    return ask(l[p1],l[p2],x,mid,k);
}
inline int find(int X,int o) 
{
    if(y[o]<tx[X]||x[o]>mx[X]) return -1;
    int Y=to[X]+sz[X]-1;
    X=to[X];
    int T=query(rt[X-1],rt[Y],1,U,y[o]);
    if(!T) return -1;return ask(rt[X-1],rt[Y],1,U,T);
}
int main()
{
    scanf("%s",S+1);n=strlen(S+1);U=n;
    for(re int i=1;i<=n;i++) ins(S[i]-'a',i,0);
    scanf("%d",&m);
    for(re int i=1;i<=m;i++)
    {
        scanf("%s",T+1),n=strlen(T+1);
        scanf("%d%d",&x[i],&y[i]);lst=1;
        for(re int j=1;j<=n;j++) ins(T[j]-'a',0,i);
    }
    for(re int i=2;i<=cnt;i++) add(fa[i],i); dfs(1);
    for(re int i=1;i<=cnt;i++)
        if(endpos[_to[i]]) rt[i]=change(rt[i-1],1,U,endpos[_to[i]]);else rt[i]=rt[i-1];
    for(re int i=1;i<=m;i++)
    {
        LL ans=0;top=0;int now=0;
        for(re int j=0;j<a[i].size();j++)
        {
            int t=a[i][j];
            if(vis[t]) continue;
            vis[t]=1,st[++top]=t;ans+=len[t];
            now=find(t,i);
            if(now!=-1&&now-len[fa[t]]>=x[i]) 
            {ans-=min(len[t],now-x[i]+1);continue;}
            while(1) 
            {
                if(vis[fa[t]]||!fa[t]) {ans-=len[fa[t]];break;}
                t=fa[t],vis[t]=1,st[++top]=t;
                now=find(t,i);
                if(now!=-1&&now-len[fa[t]]>=x[i]) {ans-=min(now-x[i]+1,len[t]);break;}
            }
        }
        for(re int j=1;j<=top;j++) vis[st[j]]=0;
        printf("%lld\n",ans);
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/asuldb/p/10284379.html