[十二省联考2019]字符串问题 后缀自动机 + 拓扑排序 + 最长路 + 倍增
题目描述:
给定一个长串 $S$,给定若干 $S$ 的子串 $a_{i}$, $b_{i}$,再给出一些 $a$ 串和 $b$ 串的支配关系.
构造一个长度最长的字符串,使得:
字符串只由 $a_{i}$ 组成.
当且仅当 $a_{i}$ 所支配的一个串 $b_{i}$ 为 $a_{j}$ 的前缀,才可将 $a_{j}$ 连到 $a_{i}$ 后面.
首先,对于求前缀,我们可以对 $S$ 的反串建立后缀自动机,这样每个 $f_{i}$ 将会转移到当前串的一个前缀,而不是后缀.
第一步,先将所有的 $a$串和$b$串都定位到后缀自动机上.
对于这一步,我们使用倍增算法.
第二部,我们将支配边进行连边(即再后缀树中对应的点和点).
顺着后缀树中的边和支配边进行转移即可.
细节部分看一下代码.
Code:
#include <cstdio> #include <algorithm> #include <cstring> #include <queue> #include <vector> #define setIO(s) freopen(s".in","r",stdin)// ,freopen(s".out","w",stdout) #define maxn 1500001 using namespace std; char str[maxn]; int debug; int length,na,nb,m; namespace SAM{ #define N 29 #define ll long long #define L l=length-l+1 #define R r=length-r+1 int last,tot; vector<int>ed[maxn]; int la[maxn],ra[maxn],h[maxn]; int ch[maxn][N],dis[maxn]; int f[maxn],trace[maxn],mk[maxn],pos[maxn]; int F[24][maxn]; int head[maxn],to[maxn],nex[maxn],edges; int ge[maxn]; int vis[maxn]; int trc; queue<int>Q; int du[maxn]; long long DP[maxn]; int idx[maxn]; int cmp(int i,int j){ if(h[i]==h[j]) return i>j; return h[i]<h[j]; } void add(int u,int v){ nex[++edges] = head[u],head[u]=edges,to[edges]=v; mk[edges]=0; //if(!debug) printf("%d %d\n",u,v); } //SAM of S void ins(int c,int i){ int np = ++tot, p = last; last = np; dis[np] = i; while(p && !ch[p][c]) ch[p][c] = np, p = f[p]; if(!p) f[np] = 1; else { int q = ch[p][c]; if(dis[q] == dis[p] + 1) f[np] = q; else { int nq = ++tot; dis[nq] = dis[p] + 1; memcpy(ch[nq],ch[q],sizeof(ch[q])); f[nq] = f[q], f[np] = f[q] = nq; while(p && ch[p][c] == q) ch[p][c] = nq,p = f[p]; } } trace[i] = np; } void DFS(int u){ F[0][u]=f[u]; for(int i=1;i<24;++i) F[i][u]=F[i-1][F[i-1][u]]; int sz=ed[u].size(); for(int i=0;i<sz;++i) DFS(ed[u][i]); ed[u].clear(); } void build(){ int l,r;scanf("%d",&na); for(int i=1;i<=na;++i){ scanf("%d%d",&l,&r); L;R; swap(l,r); la[i]=l,ra[i]=r,h[i]=r-l+1; } scanf("%d",&nb); for(int i=1;i<=nb;++i) { scanf("%d%d",&l,&r); L;R; swap(l,r); la[i+na]=l,ra[i+na]=r,h[i+na]=r-l+1; }for(int i=2;i<=tot;++i) ed[f[i]].push_back(i); trc=tot; DFS(1); for(int i=1;i<=na+nb;++i) { int p=trace[ra[i]]; for(int j=23;j>=0;--j) if(dis[F[j][p]] >= h[i]) p=F[j][p]; if(dis[p]==h[i]) pos[i]=p; else ed[p].push_back(i); } } void sol(){ int nn=tot; for(int i=2;i<=nn;++i){ if(ed[i].size()==0){ add(f[i],i); continue; } int p=0; int sz=ed[i].size(); for(int j=0;j<sz;++j) ge[++p]=ed[i][j]; // sort(ge+1,ge+1+p,cmp); //将该点对应字符串按长度排序 int lst=f[i]; for(int j=1;j<=p;++j) { add(lst,++tot); dis[tot] = h[ge[j]]; pos[ge[j]]=tot; lst=tot; } add(pos[ge[p]],i); ed[i].clear(); } } void get(){ int a,b; scanf("%d%d",&a,&b); add(pos[a],pos[b+na]); mk[edges]=1; } void solve(){ Q.push(1); du[1]=0; long long ans=0; for(int i=1;i<=edges;++i) ++du[to[i]]; while(!Q.empty()){ int u=Q.front(); Q.pop(); for(int i=head[u];i;i=nex[i]){ --du[to[i]]; long long ed=DP[u]+dis[to[i]]-(mk[i]?0:dis[u]); DP[to[i]]=max(DP[to[i]],ed); if(du[to[i]]==0) Q.push(to[i]); } } for(int i=1;i<=tot;++i) if(du[i]!=0) { printf("-1\n"); return; } for(int i=1;i<=na;++i) ans=max(ans,DP[pos[i]]); printf("%lld\n",ans); } void init(){ for(int i=1;i<=tot;++i) DP[i]=0; for(int i=1;i<=tot;++i) dis[i]=0; for(int i=1;i<=edges;++i) mk[i]=0; for(int i=1;i<=tot;++i) head[i]=0; for(int i=0;i<=tot;++i) du[i]=0; for(int i=1;i<=trc;++i) for(int j=0;j<=22;++j) F[j][i]=0; for(int i=1;i<=trc;++i) memset(ch[i],0,sizeof(ch[i])); last=tot=1,edges=0; } }; int main(){ //setIO("string9"); int T; scanf("%d",&T); while(T--){ debug=T; SAM::last=SAM::tot=1; scanf("%s",str+1),length=strlen(str+1); for(int i=length;i;--i) SAM::ins(str[i]-'a',length-i+1); SAM::trc=SAM::tot; SAM::build(); SAM::sol(); scanf("%d",&m); for(int i=1;i<=m;++i) SAM::get(); SAM::solve(); SAM::init(); } return 0; }