bzoj 5496

多有趣的一道题啊...

考场上的思路:

首先我们可以通过hash判断出每个$B$类串是几个$A$类串的前缀,从这个$B$类串向对应的$A$类串连边

然后我们直接按支配关系从$A$类串向$B$类串连边,相当于以$B$类串为中转构造了一张$A$类串的图,在这张图上跑一次最长路即可

这样做是40分(所以40分不需要后缀数组!不需要后缀自动机!)

40分代码:

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#define ull unsigned long long
#define seed 131131
using namespace std;
char s[200005];
int l[200005];
int r[200005];
ull p[200005];
ull has[200005];
bool vis[200005];
int T;
struct Edge
{
    int nxt;
    int to;
}edge[1000005];
int head[10005];
int cnt=1;
int na,nb,m;
bool used[10005];
int inr[10005];
int dis[10005];
void init()
{
    memset(inr,0,sizeof(inr));
    memset(head,-1,sizeof(head));
    memset(vis,0,sizeof(vis));
    cnt=1;
}
void adde(int l,int r)
{
    edge[cnt].nxt=head[l];
    edge[cnt].to=r;
    head[l]=cnt++;
}
int spfa(int x)
{
      memset(used,0,sizeof(used));
    memset(dis,0,sizeof(dis));
    queue <int> M;
    M.push(x);
    used[x]=1;
    dis[x]=r[x]-l[x]+1;
    int ans=dis[x];
    while(!M.empty())
    {
        int u=M.front();
        M.pop();
        for(int i=head[u];i!=-1;i=edge[i].nxt)
        {
            int to=edge[i].to;
            if(to>na)
            {
                continue;
            }
            if(dis[to]<dis[u]+r[to]-l[to]+1)
            {
                dis[to]=dis[u]+r[to]-l[to]+1;
                ans=max(ans,dis[to]);
                if(!used[to])
                {
                    used[to]=1;
                    M.push(to);
                }
            }
        }
        used[u]=0;
    }
    return ans;
}
bool tsort()
{
    queue <int> M;
    int tot=0;
    for(int i=1;i<=na;i++)
    {
        if(!inr[i])
        {
            M.push(i);
            tot++;
        }    
    }
    while(!M.empty())
    {
        int u=M.front();
        M.pop();
        for(int i=head[u];i!=-1;i=edge[i].nxt)
        {
            int to=edge[i].to;
            if(to>na)
            {
                continue;
            }
            inr[to]--;
            if(inr[to]==0)
            {
                M.push(to);
                tot++;
            }
        }
    }
    if(tot==na)
    {
        return 1;
    }
    return 0;
}
int main()
{
//    freopen("string.in","r",stdin);
//    freopen("string.out","w",stdout);
    scanf("%d",&T);
    while(T--)
    {
        init();
        scanf("%s",s+1);
        int len=strlen(s+1);
        has[0]=0;
        p[0]=1;
        for(int i=1;i<=len;i++)
        {
            has[i]=has[i-1]*seed+s[i]-'a'+1;
            p[i]=p[i-1]*seed;
        }
        scanf("%d",&na);
        for(int i=1;i<=na;i++)
        {
            scanf("%d%d",&l[i],&r[i]);
        }
        scanf("%d",&nb);
        for(int i=1;i<=nb;i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            ull hh=has[y]-has[x-1]*p[y-x+1];
            for(int j=1;j<=na;j++)
            {
                if(r[j]-l[j]+1>=y-x+1)
                {
                    if(has[l[j]+y-x]-has[l[j]-1]*p[y-x+1]==hh)
                    {
                        if(na!=1)
                        {
                            adde(i+na,j);
                        }else
                        {
                            vis[i]=1;
                        }
                    }
                }
            }
        }
        scanf("%d",&m);
        bool flag=0;
        for(int i=1;i<=m;i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            if(na!=1)
            {
                y+=na;
                adde(x,y);
            }else
            {
                if(vis[y])
                {
                    flag=1;
                }
            }
                
        }
        if(na==1)
        {
            if(flag)
            {
                printf("-1\n");
                continue;
            }else
            {
                printf("%d\n",r[1]-l[1]+1);
                continue;
            }
        }
        for(int i=1;i<=na;i++)
        {
            for(int j=head[i];j!=-1;j=edge[j].nxt)
            {
                int to=edge[j].to;
                for(int t=head[to];t!=-1;t=edge[t].nxt)
                {
                    int too=edge[t].to;
                    adde(i,too);
                    inr[too]++;
                }
            }
        }
        if(!tsort())
        {
            printf("-1\n");
            continue;
        }
        int ret=0;
        for(int i=1;i<=na;i++)
        {
            ret=max(ret,spfa(i));
        }
        printf("%d\n",ret);
    }
}

然而,这个算法很难再优化了,因为爆枚hash就已经超时了,我们需要更搞笑高效的算法。

接下来的内容需要后缀自动机与后缀树有关知识

首先有个性质:原串的parent树是反串的后缀树!

一个字符串的子串一定是一个后缀的前缀!

据此,我们可以直接建起反串的parent树(实际也就是原串的后缀树),然后在后缀树上定位出所有$A$,$B$类串,这样前缀的问题就迎刃而解了,因为后缀树上的祖宗节点一定是子代节点的前缀,同时也相当于优化了建图,因为这样我们只需在后缀树上对父子进行连边即可,避免了大量的建边操作

怎么定位?

在建立后缀自动机时,我们维护原串中每个位置在后缀自动机中所对应的节点编号,然后在后缀树上倍增即可

具体地,对于每次给出的一组$[l,r]$,由于我们是对原串反串建的后缀自动机,显然$l$对应的节点在后缀树上深度更深,我们从这个点向上倍增,倍增到深度最浅且满足$len\geq r-l+1$的点即可,此时这个点就是这个子串在后缀树上定位到的点!

然后我们记录一个节点被不同的串定位到的次数即可

同时考虑另一个问题:由于这样建起的后缀树有大量压缩,因此对于两个不同的串,可能会被定位到后缀树上的同一个点,这样显然是不对的

因此我们考虑展开压缩:在后缀树上如果一个点被不同的串定位了,那么我们把所有这些串按照长度排序后对对每种长度分别建一个点,保证长度从小到大新建的点深度由浅到深,同时不断由父节点向子节点连边,边权为0建图即可

最后支配关系直接找到两个串在后缀树上定位到的节点后连边,边权为$A$类串长度

然后跑拓扑最长路即可

(据说可以后缀数组+st表+主席树优化建图搞,不过窝不会...)

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#include <vector>
#include <set>
#define ll long long
using namespace std;
struct Edge
{
    int nxt;
    int to;
    int val;
}edge[800005];
struct node
{
    int po,ilen;
    node (){}
    node (int a,int b):po(a),ilen(b){}
    friend bool operator < (node a,node b)
    {
        return a.po==b.po?a.ilen<b.ilen:a.po<b.po;
    }
};
set <node> S;
int head[800005];
int cnt=1;
int tranc[400005][27];
int vis[400005];
int l[400005],r[400005],pos[400005];
int va[800005];
int pre[400005];
bool used[800005];
int tf[400005];//字符串中某个位置向后缀自动机中节点的映射
int len[400005];
int inr[800005];
char ch[400005];
ll dis[800005];
int f[400005][25];
vector <int> v[400005];
vector <node> rS[400005];
int tot,las,temptot;
int T,n;
int na,nb,m;
void init()
{
    for(int i=1;i<=tot;i++)head[i]=va[i]=used[i]=inr[i]=0,dis[i]=-0x3f3f3f3f;
    for(int i=1;i<=temptot;i++)rS[i].clear(),v[i].clear(),vis[i]=pre[i]=len[i]=0,memset(tranc[i],0,sizeof(tranc[i]));
    S.clear();
    tot=las=cnt=1;
}
void add(int l,int r,int w)
{
    edge[cnt].nxt=head[l];
    edge[cnt].to=r;
    edge[cnt].val=w;
    head[l]=cnt++;
}
void ins(int c,int o)
{
    int nwp=++tot;
    len[nwp]=len[las]+1;
    tf[o]=nwp;
    int lsp;
    for(lsp=las;lsp&&!tranc[lsp][c];lsp=pre[lsp])tranc[lsp][c]=nwp;
    if(!lsp)pre[nwp]=1;
    else 
    {
        int lsq=tranc[lsp][c];
        if(len[lsq]==len[lsp]+1)pre[nwp]=lsq;
        else
        {
            int nwq=++tot;
            memcpy(tranc[nwq],tranc[lsq],sizeof(tranc[lsq]));
            pre[nwq]=pre[lsq];
            pre[lsq]=pre[nwp]=nwq;
            len[nwq]=len[lsp]+1;
            while(lsp&&tranc[lsp][c]==lsq)tranc[lsp][c]=nwq,lsp=pre[lsp];
        }
    }
    las=nwp;
}
void buildtree()
{
    for(int i=2;i<=tot;i++)v[pre[i]].push_back(i),f[i][0]=pre[i];
    f[1][0]=1;
}
int Jump(int st,int di)
{
    for(int i=20;i>=0;i--)if(len[f[st][i]]>=di&&f[st][i]!=1)st=f[st][i];
    if(S.find(node(st,di))==S.end())S.insert(node(st,di)),vis[st]++;
    return st;
}
void rebuild(int x,int fx)
{
    if(fx)add(fx,x,0),inr[x]++;
    if(x==1){for(int i=0;i<v[x].size();i++)rebuild(v[x][i],x);return;}
    sort(rS[x].begin(),rS[x].end());
    int now=0;
    for(int i=1;i<rS[x].size();i++)if(rS[x][i].po!=rS[x][i-1].po){now=i;break;}
    int las=x;
    while(vis[x]>1)
    {
        tot++,vis[x]--;
        pos[rS[x][now].ilen]=tot;
        for(int i=now+1;i<rS[x].size();i++)
        {
            if(rS[x][i].po!=rS[x][i-1].po){now=i;break;}
            else pos[rS[x][i].ilen]=tot;
        }
        add(las,tot,0),inr[tot]++;
        las=tot;
    }
    for(int i=0;i<v[x].size();i++)rebuild(v[x][i],las);
}
ll spfa()
{
    queue <int> M;
    M.push(1);
    dis[1]=0;
    int cct=1;
    ll ans=0;
    while(!M.empty())
    {
        int u=M.front();
        M.pop();
        ans=max(ans,dis[u]+(ll)va[u]);
        for(int i=head[u];i;i=edge[i].nxt)
        {
            int to=edge[i].to;
            dis[to]=max(dis[u]+(ll)edge[i].val,dis[to]);
            inr[to]--;
            if(!inr[to])M.push(to),cct++;
        }
    }
    if(cct==tot)return ans;
    else return -1;
}
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        init();
        scanf("%s",ch+1);
        n=strlen(ch+1);
        for(int i=n;i>=1;i--)ins(ch[i]-'a'+1,i);
        buildtree();
        for(int i=1;i<=20;i++)for(int j=1;j<=tot;j++)f[j][i]=f[f[j][i-1]][i-1];
        scanf("%d",&na);
        for(int i=1;i<=na;i++)
        {
            scanf("%d%d",&l[i],&r[i]);
            pos[i]=Jump(tf[l[i]],r[i]-l[i]+1);
        }
        scanf("%d",&nb);
        for(int i=na+1;i<=na+nb;i++)
        {
            scanf("%d%d",&l[i],&r[i]);
            pos[i]=Jump(tf[l[i]],r[i]-l[i]+1);
        }
        for(int i=1;i<=na+nb;i++)rS[pos[i]].push_back(node(r[i]-l[i]+1,i));
        temptot=tot;
        rebuild(1,0);
        for(int i=1;i<=na;i++)va[pos[i]]=r[i]-l[i]+1;
        scanf("%d",&m);
        for(int i=1;i<=m;i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            y+=na;
            add(pos[x],pos[y],r[x]-l[x]+1),inr[pos[y]]++;
        }
        printf("%lld\n",spfa());
    }
    return 0;
}

 

posted @ 2019-07-04 09:04  lleozhang  Views(63)  Comments(0Edit  收藏
levels of contents