LINK

考虑每个 A i A_i Ai A j A_j Aj连边,当且仅当存在一个被 A i A_i Ai支配的串 B z B_z Bz,使得 B z B_z Bz A j A_j Aj的前缀

这样得到了一个图,只需对这个图拓扑排序求最长路即可.

T 1 T1 T1

先可以对所有的支配关系由 A A A连向 B B B,费用为 0 0 0(此时 B B B串相当于一个中转点)

那么现在只需要考虑 B i B_i Bi是哪些 A A A串的前缀然后连边

可以很容易的知道,在后缀自动机上,父亲是儿子的后缀

如果我们对反串建 S A M SAM SAM,反串 S A M SAM SAM中父亲是儿子的后缀,意味着原串中父亲是儿子的前缀

所以只需要由 B i B_i Bi向所有子树中的 A A A串节点连边即可

此时复杂度降为 O ( n 2 ) O(n^2) O(n2),究其原因还是边太多了不能暴力连

40分代码

T 2 T2 T2

注意到 S A M SAM SAM中的一个节点对应多个子串,我们把这些子串按照长度排序

若长度相同,则按 A A A类串在前, B B B类串在后的顺序排序

设排序后变成这个样子 a 1 , a 2 , b 1 , a 3 , b 2 , a 4 a_1,a_2,b_1,a_3,b_2,a_4 a1,a2,b1,a3,b2,a4

那么我们就连边 ( i , a 1 ) , ( i , a 2 ) , ( i , b 1 ) , ( b 1 , a 3 ) , ( b 1 , b 2 ) , ( b 2 , a 4 ) (i,a_1),(i,a_2),(i,b_1),(b_1,a_3),(b_1,b_2),(b_2,a_4) (i,a1),(i,a2),(i,b1),(b1,a3),(b1,b2),(b2,a4)

其中 i i i表示 S A M SAM SAM中的这个节点,并无实际意义,只是起到祖先和儿子的连通作用

设祖先有个 B B B类串能转移到 a 1 a_1 a1,那么也必然能转移到 a 2 a_2 a2,因为他们互为前缀关系

然后,祖先中的 B B B类串也可以不选择 a 1 , a 2 a_1,a_2 a1,a2,而是转移到 b 1 b_1 b1,在下面的串再找机会

所以我们的连边策略就是记录一个 l a s t b lastb lastb表示上次的最长 B B B类串

然后 l a s t b lastb lastb向遇到的所有串都连边,直到遇到更长的 B B B类串再更新 l a s t b lastb lastb

非常厉害的技巧,类似前缀和连边

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e6+10;
int zi[maxn][27],fa[maxn],l[maxn],ed[maxn],id = 1, las = 1;
char s[maxn];
int le,m,na,nb;
void insert(int c)
{
	int p = las, np = ++id; las = id;
	l[np] = l[p]+1; 
	for( ; p&&zi[p][c]==0;p=fa[p] )	zi[p][c] = np;
	if( !p 	)	fa[np] = 1;
	else
	{
		int q = zi[p][c];
		if( l[q]==l[p]+1 )	fa[np] = q;
		else
		{
			int nq = ++id;
			memcpy( zi[nq],zi[q],sizeof zi[q] );
			fa[nq] = fa[q], l[nq] = l[p]+1;
			fa[np] = fa[q] = nq;
			for( ; p&&zi[p][c]==q;p=fa[p] )	zi[p][c] = nq;	
		}
	}
}
vector<int>vec[maxn];
int up[maxn][22],in[maxn],lst[maxn],isa[maxn],sz;
int a[maxn],b[maxn];
ll dis[maxn];
bool com(int a,int b)
{
	return l[a]>l[b]||(l[a]==l[b]&&isa[a]>isa[b] );
}
void isok(int aorb)
{
	int L,R; scanf("%d%d",&L,&R);
	int x = ed[L];
	for(int i=21;i>=0;i--)
		if( up[x][i] && l[up[x][i]]>=R-L+1 )	x = up[x][i];
	isa[++sz] = aorb, l[sz] = R-L+1;
	vec[x].push_back( sz ); in[sz]++;
}
void find_A_B()
{
	sz = id;
	scanf("%d",&na );
	for(int i=1;i<=na;i++)	isok( 1 ),a[i] = sz;
	scanf("%d",&nb );
	for(int i=1;i<=nb;i++)	isok( 0 ),b[i] = sz;
	for(int i=1;i<=id;i++)	sort( vec[i].begin(),vec[i].end(),com );
	for(int i=1;i<=id;i++)
	{
		int last = i;
		for(int j=vec[i].size()-1;j>=0;j--)
		{
			int now = vec[i][j]; 
			vec[last].push_back( now ); in[now]++;
			if( !isa[now] )	last = now;
		}
		lst[i] = last;
	}
	for(int i=2;i<=id;i++)
		vec[lst[fa[i]]].push_back( i ),in[i]++;
	for(int i=1;i<=sz;i++)
		if( !isa[i] )	l[i] = 0;
	scanf("%d",&m );
	for(int i=1;i<=m;i++)
	{
		int L,R; scanf("%d%d",&L,&R );
		vec[a[L]].push_back( b[R] ); in[b[R]]++;
	}
}
void tuopu()
{
	queue<int>q; 
	for(int i=1;i<=sz;i++)
		if( !in[i] )	q.push( i );
	int shu = 0; ll ans = 0;
	while( !q.empty() )
	{
		int u = q.front(); q.pop();
		shu++; ans = max( ans,dis[u]+l[u] );
		for(auto v:vec[u] )
		{
			dis[v] = max( dis[v],dis[u]+l[u] );
			if( --in[v]==0 )	q.push( v );
		}	
	}
	if( shu!=sz )	printf("-1\n");
	else	printf("%lld\n",ans );
}
void init()
{
	for(int i=1;i<=id;i++)
	{
		memset( zi[i],0,sizeof zi[i] );
		l[i] = fa[i] = 0;
	}
	for(int i=0;i<=sz;i++)
	{
		vec[i].clear();
		isa[i] = 0;
		in[i] = dis[i] = l[i] = lst[i] = 0;
	}
	id = las = 1; sz = 0;
}
int main()
{
	int T; cin >> T;
	while( T-- )
	{
		scanf("%s",s+1 ); le = strlen( s+1 );
		for(int i=le;i>=1;i--)
			insert( s[i]-'a' ), ed[i] = las;
		for(int i=1;i<=id;i++)	up[i][0] = fa[i];
		for(int i=1;i<=21;i++)
		for(int j=1;j<=id;j++)
			up[j][i] = up[up[j][i-1]][i-1];
		find_A_B();
		tuopu();
		init();
	}
}