【loj3049】【十二省联考】字符串问题
## Description
大概就是说给你一个串\(s\),然后给定\(n_A\)个\(A\)串和\(n_B\)个\(B\)串,每个\(A\)串或者\(B\)串都是\(S\)的一个子串(形如\(s[l...r]\)),然后有\(m\)对支配关系,每对\((i,j)\)表示\(A_i\)支配\(B_j\),现在求一个最长的字符串的长度,满足这个字符串可以被划分成若干个\(A\)串,并且划分方案中相邻的两个\(A\)串\(A_{id}\)和\(A_{id-1}\)满足\(A_{id-1}\)的一个前缀是\(A_{id}\)所支配的一个\(B\)串
数据范围:\(n_A,n_B,m,|s|\leq 2*10^5\)
Solution
虽然说。。很多人都说这题很水但是。。写完怎么感觉像重新学了一遍sam一样==(本来学的就不好。。)
首先补一下档好了。。终于清晰起来的概念:
- sam的right集是结束位置集合(就是一个串\(t\)在原串\(s\)中出现了若干次,每次出现的最后一个字符的下标的集合),sam中的一个节点代表的一堆字符串的right集是一样的,一个节点\(x\)的suffix link指向的是真包含\(x\)的right集的最小的那个right集对应的节点
- sam中一个节点的len表示的是这个节点代表字符串中最长的那个的长度,每一个节点能代表的字符串的长度是一段连续的区间,为\([len[suf[x]]+1,len[x]]\)
- sam能建出来的后缀树是反串的后缀树,而后缀树中的一个节点与其子树内节点的关系是:一个节点是其子树内节点的前缀,可以简单理解为一棵压缩过的\(trie\)(下面只有一个状态点的边全部缩起来),里面存的是反串的所有后缀
好了
然后看回这题,最简单粗暴的想法就是把能相邻的\(A\)串连边,然后在这个图里面找环,找得到就是\(-1\),找不到说明是个DAG那么直接找一条最长路就好了
现在的问题就是怎么建图
注意到要求能连过去的\(A\)串必须有一个前缀是前一个\(A\)串的支配\(B\)串,也就是说如果我们把后缀树建出来的话,我们定位\(A\)串、\(B\)串,那么一个\(A\)串就能向它的支配\(B\)串所对应的节点的子树内的所有\(A\)串连边
所以我们只要把后缀树建出来,然后根据\(len\)在上面倍增跳,找到一个串在哪个点上面或者哪条边上面,记录一下再连边就行了
感觉实现上还是有挺大启发(毕竟我没有任何码力==):
- 注意对应点相同的串要对到同一个点去。。不能新增节点那样会出锅。。然后对应点相同的串可能是从不同的sam节点开始往上跳跳过来的,这点在写的过程中脑抽wa了好久。。实现上的话可以对每个后缀树上节点记录其到父亲这条边上面挂了多少个节点,当所有的节点都挂完之后再一条条边处理,按照长度排序,再连起来
- 最长路的计算:我们在处理\(m\)对支配关系的时候连边是\(A\)到\(B\)然后因为树上的边已经连好了所以可以直接走到子树里面去不需要特殊处理,但是要求\(A\)串要么走到一个\(B\)串再走到一个\(A\)串,要么不算这个\(A\)串的贡献继续走到子树里面的\(A\)串,这个限制可以通过对每个\(A\)串建虚点实现,实点向虚点连边,然后把贡献全部变成虚点的点权即可,其他点点权均为\(0\),支配关系就从\(A\)的虚点连到\(B\)对应的实点就好了(\(B\)不需要建虚点)
Code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#define ll long long
#define Pr pair<int,int>
#define mp make_pair
using namespace std;
const int N=2e5+10;
char s[N];
int L[N*2],R[N*2];
int rec1[N];
int pos[N],node[N*2],node1[N];
int lens,nA,nB,m,T;
ll ans;
namespace G{/*{{{*/
const int N=::N*5;
struct xxx{
int y,nxt,dis;
}a[N*10];
int h[N],deg[N],pre[N],val[N];
int rec1[N];
ll dis[N];
int cnt,n,tot;
queue<int> q;
void init(){
for (int i=1;i<=n;++i)
h[i]=-1,deg[i]=0,dis[i]=0,val[i]=0,rec1[i]=0,pre[i]=0;
n=0;
}
int newnode(int L){val[++n]=L;return n;}
void add(int x,int y){++deg[y]; a[++tot].y=y; a[tot].nxt=h[x]; h[x]=tot;}
void build_t(){
for (int i=1;i<=n;++i)
if (pre[i]) G::add(pre[i],i);
}
ll solve(){
int u,v;
for (int i=1;i<=n;++i){
if (!deg[i]) q.push(i);
dis[i]=val[i];
}
cnt=0;
while (!q.empty()){
v=q.front(); q.pop();
++cnt;
for (int i=h[v];i!=-1;i=a[i].nxt){
u=a[i].y;
dis[u]=max(dis[u],dis[v]+val[u]);
--deg[u];
if (!deg[u]) q.push(u);
}
}
if (cnt!=n) return -1;
ll ret=-1;
for (int i=1;i<=n;++i) ret=max(ret,dis[i]);
return ret;
}
}/*}}}*/
namespace Sam{/*{{{*/
const int N=::N*2,C=26,TOP=20;//remember!!!!
int ch[N][C],suf[N],len[N];
int trans[N][C],pos[N];
vector<Pr> lis[N];
int f[N][TOP+1];
int str[N];
int tot,last;
void init(){
for (int i=1;i<=tot;++i){
lis[i].clear();
for (int c=0;c<C;++c) ch[i][c]=0,trans[i][c]=0;
suf[i]=len[i]=pos[i]=0;
str[i]=0;
}
tot=last=1; G::newnode(0);
}
int newnode(int L){len[++tot]=L; G::newnode(0); return tot;}
int expand(int c){
int p=last,np=newnode(len[p]+1),nq,q;
str[len[np]]=c; pos[np]=len[np];
for (;p&&!ch[p][c];p=suf[p]) ch[p][c]=np;
if (!p) suf[np]=1;
else{
q=ch[p][c];
if (len[q]!=len[p]+1){
nq=newnode(len[p]+1);
pos[nq]=pos[np];
memcpy(ch[nq],ch[q],sizeof(ch[nq]));
suf[nq]=suf[q]; suf[q]=suf[np]=nq;
for (;p&&ch[p][c]==q;p=suf[p]) ch[p][c]=nq;
}
else
suf[np]=q;
}
last=np;
return last;
}
void dfs(int fa,int x){
f[x][0]=fa;
for (int i=1;i<=TOP;++i)
f[x][i]=f[f[x][i-1]][i-1];
for (int c=0;c<C;++c)
if (trans[x][c]){
G::pre[trans[x][c]]=x;
dfs(x,trans[x][c]);
}
}
void build(){
for (int i=2;i<=tot;++i)
trans[suf[i]][str[pos[i]-len[suf[i]]]]=i;
dfs(0,1);
}
void find_pos(int x,int l,int id){
int tmp=x,ret;
for (int i=TOP;i>=0;--i){
if (len[f[tmp][i]]>=l) tmp=f[tmp][i];
}
//if (len[tmp]==l) ret=tmp;
x=tmp; l-=len[x];
lis[x].push_back(mp(l,id));
}
void link(){
int id,sz;
for (int x=1;x<=tot;++x){
sort(lis[x].begin(),lis[x].end());
sz=lis[x].size();
for (int i=0;i<sz;++i){
id=lis[x][i].second;
if (i&&lis[x][i].first==lis[x][i-1].first)
::node[id]=::node[lis[x][i-1].second];
else
::node[id]=lis[x][i].first==0?x:G::newnode(0);
if (id<=nA){
if (!G::rec1[::node[id]]){
G::rec1[::node[id]]=G::newnode(R[id]-L[id]+1);
G::add(::node[id],G::rec1[::node[id]]);
}
node1[id]=G::rec1[::node[id]];
}
if (lis[x][i].first&&G::pre[x]!=::node[id]){
G::pre[::node[id]]=G::pre[x];
G::pre[x]=::node[id];
}
}
}
}
}/*}}}*/
void prework(){
G::init();
Sam::init();
for (int i=lens;i>=1;--i)
pos[i]=Sam::expand(s[i]-'a');
Sam::build();
for (int i=1;i<=nA+nB;++i)
Sam::find_pos(pos[L[i]],R[i]-L[i]+1,i);
Sam::link();
G::build_t();
}
int main(){
#ifndef ONLINE_JUDGE
freopen("a.in","r",stdin);
#endif
int x,y;
scanf("%d",&T);
memset(G::h,-1,sizeof(G::h));
for (int o=1;o<=T;++o){
scanf("%s",s+1);
lens=strlen(s+1);
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) scanf("%d%d",&L[i+nA],&R[i+nA]);
prework();
scanf("%d",&m);
//printf("\n");
for (int i=1;i<=m;++i){
scanf("%d%d",&x,&y);
G::add(node1[x],node[nA+y]);
}
ans=G::solve();
printf("%lld\n",ans);
}
}