多有趣的一道题啊...
考场上的思路:
首先我们可以通过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 "); continue; }else { printf("%d ",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 "); continue; } int ret=0; for(int i=1;i<=na;i++) { ret=max(ret,spfa(i)); } printf("%d ",ret); } }
然而,这个算法很难再优化了,因为爆枚hash就已经超时了,我们需要更搞笑高效的算法。
接下来的内容需要后缀自动机与后缀树有关知识
首先有个性质:原串的parent树是反串的后缀树!
一个字符串的子串一定是一个后缀的前缀!
据此,我们可以直接建起反串的parent树(实际也就是原串的后缀树),然后在后缀树上定位出所有$A$,$B$类串,这样前缀的问题就迎刃而解了,因为后缀树上的祖宗节点一定是子代节点的前缀,同时也相当于优化了建图,因为这样我们只需在后缀树上对父子进行连边即可,避免了大量的建边操作
怎么定位?
在建立后缀自动机时,我们维护原串中每个位置在后缀自动机中所对应的节点编号,然后在后缀树上倍增即可
具体地,对于每次给出的一组$[l,r]$,由于我们是对原串反串建的后缀自动机,显然$l$对应的节点在后缀树上深度更深,我们从这个点向上倍增,倍增到深度最浅且满足$lengeq 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 ",spfa()); } return 0; }