题目链接:
首先考虑最暴力的做法就是对于每个$B$串存一下它是哪些$A$串的前缀,然后按每组支配关系连边,做一遍拓扑序DP即可。
但即使忽略判断前缀的时间,光是连边的时间就会爆炸,显然不能暴力连边。
对于前缀不好解决,可以将字符串翻转然后变成判断是否是后缀。
可以发现对于后缀自动机的$parent$树,每个节点是子树内所有节点的后缀。
那么我们可以利用$parent$树来优化建图过程,将树上每个点向子节点连边。
对于每个$A$串和$B$串在后缀自动机上匹配出对应节点,如果是$A$串就新建节点并将$parent$树上的对应节点连向它,如果是$B$串就新建节点连向$parent$树上的对应节点。
对于$80\%$的数据,因为保证$B$串比$A$串短,所以对于$parent$树上的一个节点既有出点又有入点是合法的($100\%$的部分最后再说)。
但现在只解决了建图的问题,字符串在后缀自动机上匹配的时间复杂度依旧无法保证,如何找到每个$A,B$串对应的节点?
因为$[l,r]$的串一定是$[1,r]$的串的后缀,所以$[l,r]$的串在$parent$树上对应的节点一定是$[1,r]$的串对应节点的祖先,那么我们可以记录一下所有$[1,r]$的串在$parent$树上对应的节点,然后倍增往上跳来找到$[l,r]$对应的节点。
对于$100\%$的数据,不保证$B$串比$A$串短,那么就可能存在一个$B$串比一个$A$串长,但这两个串在$parent$树上对应的节点相同。如果像上述方式连边的话,就会导致这个$B$串能转移到$A$串。
对于这种情况,我们将连在$parent$树上同一点的所有串存起来,按长度从小到大为第一关键字、先$B$串后$A$串为第二关键字排序,将$parent$树上每个点与父节点之间的边拆开,在这两点之间建一串点,分别与排完序的$A,B$串新建点相连,再将这一串点依次相连,这样有了上下制约关系就不会导致上述情况发生了。
#include<set> #include<map> #include<queue> #include<stack> #include<cmath> #include<cstdio> #include<bitset> #include<vector> #include<cstring> #include<iostream> #include<algorithm> #define ll long long using namespace std; int len[1200010]; int pre[1200010]; int tr[1200010][26]; int fa[1200010][19]; int num[200010]; int val[1200010]; int head[1200010]; int to[1400010]; int next[1400010]; int cnt; int last; int T; int tot; char ch[200010]; int na,nb; int n,m; int l,r; int x,y; int a[200010]; int b[200010]; int d[1200010]; queue<int>q; int vis[1200010]; ll f[1200010]; int sum; struct lty { int len,num,opt; lty(){} lty(int LEN,int NUM,int OPT){len=LEN,num=NUM,opt=OPT;} bool operator <(lty a)const { return len==a.len?num>a.num:len<a.len; } }; vector<lty>v[400010]; inline void add(int x,int y) { next[++tot]=head[x]; head[x]=tot; to[tot]=y; d[y]++; } inline void insert(int x,int id) { int p=last; int np=++cnt; memset(tr[np],0,sizeof(tr[np])); last=np; len[np]=len[p]+1; num[id]=np; for(;p&&!tr[p][x];p=pre[p]) { tr[p][x]=np; } if(!p) { pre[np]=1; } else { int q=tr[p][x]; if(len[q]==len[p]+1) { pre[np]=q; } else { int nq=++cnt; memset(tr[nq],0,sizeof(tr[nq])); len[nq]=len[p]+1; pre[nq]=pre[q]; memcpy(tr[nq],tr[q],sizeof(tr[q])); pre[q]=pre[np]=nq; for(;p&&tr[p][x]==q;p=pre[p]) { tr[p][x]=nq; } } } } inline void build() { for(int i=2;i<=cnt;i++) { fa[i][0]=pre[i]; } for(int j=1;j<=18;j++) { for(int i=2;i<=cnt;i++) { fa[i][j]=fa[fa[i][j-1]][j-1]; } } } inline int ST(int x,int dis) { for(int i=18;i>=0;i--) { if(len[fa[x][i]]>=dis) { x=fa[x][i]; } } return x; } inline void clear() { for(int i=1;i<=sum;i++) { v[i].clear(); } } inline void solve() { scanf("%s",ch+1); n=strlen(ch+1); for(int i=1;i<=n;i++) { insert(ch[n-i+1]-'a',i); } build(); sum=cnt; scanf("%d",&na); for(int i=1;i<=na;i++) { scanf("%d%d",&l,&r); a[i]=++cnt; int p=ST(num[n-l+1],r-l+1); v[p].push_back(lty(r-l+1,a[i],0)); val[a[i]]=r-l+1; } scanf("%d",&nb); for(int i=1;i<=nb;i++) { scanf("%d%d",&l,&r); b[i]=++cnt; int p=ST(num[n-l+1],r-l+1); v[p].push_back(lty(r-l+1,b[i],1)); } for(int i=2;i<=sum;i++) { sort(v[i].begin(),v[i].end()); int sz=v[i].size(); int now=pre[i]; for(int j=0;j<sz;j++) { cnt++; add(now,cnt); if(!v[i][j].opt) { add(cnt,v[i][j].num); } else { add(v[i][j].num,cnt); } now=cnt; } add(now,i); } scanf("%d",&m); for(int i=1;i<=m;i++) { scanf("%d%d",&x,&y); add(a[x],b[y]); } for(int i=1;i<=cnt;i++) { f[i]=val[i]; if(!d[i]) { q.push(i); vis[i]=1; } } while(!q.empty()) { int now=q.front(); q.pop(); for(int i=head[now];i;i=next[i]) { if(f[to[i]]<f[now]+val[to[i]]) { f[to[i]]=f[now]+val[to[i]]; } d[to[i]]--; if(!d[to[i]]) { q.push(to[i]); vis[to[i]]=1; } } } ll ans=0; for(int i=1;i<=cnt;i++) { if(!vis[i]) { printf("-1 "); clear(); return; } ans=max(ans,f[i]); } printf("%lld ",ans); clear(); } inline void init() { cnt=last=1,tot=0; memset(head,0,sizeof(head)); memset(to,0,sizeof(to)); memset(val,0,sizeof(val)); memset(num,0,sizeof(num)); memset(pre,0,sizeof(pre)); memset(tr[1],0,sizeof(tr[1])); memset(next,0,sizeof(next)); memset(len,0,sizeof(len)); memset(d,0,sizeof(d)); memset(vis,0,sizeof(vis)); memset(f,0,sizeof(f)); } int main() { int T; scanf("%d",&T); while(T--) { init(); solve(); } }