目录
@description@
现有一个字符串 S。
Tiffany 将从中划分出 na 个子串作为 A 类串,第 i 个 Ai = S[la[i]...ra[i]]。
Yazid 将从中划分出 nb 个子串作为 B 类串,第 i 个 Bi = S[lb[i]...rb[i]]。
给定 m 组支配关系 (x, y),表示第 x 的 A 类串支配第 y 的 B 类串。
请使用任意多个 A 类串拼接起来得到最长的目标串 T,满足对于两个相邻的 A 类串,前一个 A 类串支配的某个 B 类串是后一个 A 类串的前缀。
如果无限长,输出 -1。
输入格式
从标准输入读入数据。
单个测试点中包含多组数据,输入的第一行包含一个非负整数 T 表示数据组数。接下来依次描述每组数据,对于每组数据:
第 1 行一个只包含小写字母的字符串 S。
第 2 行一个非负整数 na,表示 A 类串的数目。接下来 na 行,每行 2 个用空格隔开的整数。
这部分中第 i 行的两个数分别为 la[i], ra[i],描述第 i 个 A 类串。
保证 1 <= la[i] <= ra[i] <= |S|。
接下来一行一个非负整数 nb,表示 B 类串的数目。接下来 nb 行,每行 2 个用空格隔开的整数。
这部分中第 i 行的两个数分别为 lb[i], rb[i],描述第 i 个 B 类串。
保证 1 <= lb[i] <= rb[i] <= |S|。
接下来一行一个非负整数 m,表示支配关系的组数。接下来 m 行,每行 2 个用空格隔开的整数。
这部分中每行的两个整数 x, y,描述一对 (x, y) 的支配关系,具体意义见「题目描述」。
保证 1 <= x <= na,1 <= y <= nb。保证所有支配关系两两不同,即不存在两组支配关系的 x, y 均相同。
输出格式
输出到标准输出。
依次输出每组数据的答案,对于每组数据:
一行一个整数表示最大串长。特别地,如果满足限制的串可以是无限长的,则请输出 -1。
样例输入 1
3
abaaaba
2
4 7
1 3
1
3 4
1
2 1
abaaaba
2
4 7
1 3
1
7 7
1
2 1
abbaabbaab
4
1 5
4 7
6 9
8 10
3
1 6
10 10
4 6
5
1 2
1 3
2 1
3 3
4 1
样例输出 1
7
-1
13
样例说明 1
对于第 1 组数据,A 类串有 aaba 与 aba,B 类串有 aa,且 A2 支配 B1。我们可以找到串 abaaaba,它可以拆分成 A2 + A1,且 A1 包含由 A2 所支配的 B1 作为前缀。可以证明不存在长度更大的满足限制的串。
对于第 2 组数据,与第 1 组数据唯一不同的是,唯一的 B 类串为 a。容易证明存在无限长的满足限制的串。
对于第 3 组数据,容易证明 abbaabbaaaabb 是最长的满足限制的串。
数据范围与提示
对于所有测试点中的每一组数据,保证:1 <= |S| <= 2*10^5,na, nb <= 2*10^5,m <= 2*10^5。
且 |S|, na, nb, m 的总和的总和分别不会超过该测试点中对应的单组数据的限制的 10 倍。
@solution@
每一个 Ai 后面能够接的 A 类串的集合是固定的,于是我们可以将 Ai 向它后面能够接的 A 类串连边。
跑一个简单的拓扑排序,如果有环则无解,否则可以边排序边 DAG 上 dp 求最大值。
现在考虑优化一下建边。
我们可以每一个 A 类串向它支配的 B 类串连边,每一个 B 类串向以这个 B 类串为前缀的 A 类串连边。
前半部分的连边是 O(m) 的,我们继续考虑优化后面部分。
如果 Bj = S[lb[j]...rb[j]] 是 Ai = S[la[i]...ra[i]] 的前缀,则这个条件其实与 |Ai| >= |Bj| 且 lcp(lb[j], la[i]) >= |Bj| 等价。
而通过后缀数组可以得知,与后缀 lb[j] 的 lcp >= 某个值的后缀实际上形成一个区间。
于是可以考虑用可持久化线段树辅助我们连边。
具体来说,我们将 A、B 按照其长度一起排序,从大到小依次考虑串,先考虑 A 类串再考虑 B 类串。
对于 A 类串,直接丢入线段树中其 rank 对应的位置即可。
对于 B 类串,我们首先二分找到 lcp >= |Bj| 的对应区间,然后向这个区间连边即可。
时间复杂度和空间复杂度都是 O(nlogn)。
@accepted code@
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 200000;
struct edge{
int to; edge *nxt;
}edges[80*MAXN + 5], *adj[45*MAXN + 5], *ecnt;
int ind[45*MAXN + 5];
void addedge(int u, int v) {
edge *p = (++ecnt);
p->to = v, p->nxt = adj[u], adj[u] = p;
ind[v]++;
// printf("! %d %d\n", u, v);
}
struct node{
int l, r, id;
node(int _l=0, int _r=0):l(_l), r(_r) {}
int len() {return r - l + 1;}
friend bool operator < (node x, node y) {return x.len() < y.len();}
}a[MAXN + 5], b[MAXN + 5];
char S[MAXN + 5];
int sa[MAXN + 5], rnk[MAXN + 5], c[MAXN + 5];
int nsa[MAXN + 5], nrnk[MAXN + 5];
int na, nb, m, lenS;
void get_sa(int n, int m) {
for(int i=0;i<m;i++) c[i] = 0;
for(int i=0;i<n;i++) c[S[i]]++;
for(int i=1;i<m;i++) c[i] += c[i-1];
for(int i=n-1;i>=0;i--) sa[--c[S[i]]] = i;
rnk[sa[0]] = 0;
for(int i=1;i<n;i++) rnk[sa[i]] = rnk[sa[i-1]] + (S[sa[i]] != S[sa[i-1]]);
for(int k=1;rnk[sa[n-1]]!=n-1;k<<=1) {
int cnt = 0;
for(int i=n-k;i<n;i++) nsa[cnt++] = i;
for(int i=0;i<n;i++)
if( sa[i] >= k ) nsa[cnt++] = sa[i] - k;
for(int i=0;i<n;i++) nrnk[i] = rnk[i];
for(int i=0;i<n;i++) c[i] = 0;
for(int i=0;i<n;i++) c[nrnk[i]]++;
for(int i=1;i<n;i++) c[i] += c[i-1];
for(int i=n-1;i>=0;i--) sa[--c[nrnk[nsa[i]]]] = nsa[i];
rnk[sa[0]] = 0;
for(int i=1;i<n;i++) rnk[sa[i]] = rnk[sa[i-1]] + (nrnk[sa[i]] != nrnk[sa[i-1]] || nrnk[sa[i]+k] != nrnk[sa[i-1]+k]);
}
}
int ht[MAXN + 5];
void get_height(int n) {
int k = 0;
for(int i=0;i<n;i++) {
if( rnk[i] == 0 ) ht[rnk[i]] = 0;
else {
if( k ) k--;
while( S[i+k] == S[sa[rnk[i]-1]+k] )
k++;
ht[rnk[i]] = k;
}
}
}
int st[20][MAXN + 5], lg[MAXN + 5];
void get_st(int n) {
for(int i=0;i<n;i++)
st[0][i] = ht[i];
for(int i=2;i<=n;i++)
lg[i] = lg[i>>1] + 1;
for(int j=1;j<20;j++) {
int t = (1<<(j-1));
for(int i=0;i+t<n;i++)
st[j][i] = min(st[j-1][i], st[j-1][i+t]);
}
}
int lcp(int l, int r) {
if( l == r ) return MAXN;
if( l > r ) swap(l, r); l++;
int k = lg[r-l+1], p = (1<<k);
return min(st[k][l], st[k][r-p+1]);
}
int ch[2][40*MAXN + 5], root, ncnt;
void link_edge(int rt, int l, int r, const int &ql, const int &qr, const int &x) {
if( l > qr || r < ql || (!rt) ) return ;
if( ql <= l && r <= qr ) {
addedge(x, rt + na + nb);
return ;
}
int mid = (l + r) >> 1;
link_edge(ch[0][rt], l, mid, ql, qr, x);
link_edge(ch[1][rt], mid + 1, r, ql, qr, x);
}
int insert(int rt, int l, int r, const int &p, const int &x) {
int q = (++ncnt);
ch[0][q] = ch[0][rt], ch[1][q] = ch[1][rt];
if( l == r ) {
if( rt ) addedge(q + na + nb, rt + na + nb);
addedge(q + na + nb, x);
}
else {
int mid = (l + r) >> 1;
if( p <= mid ) ch[0][q] = insert(ch[0][rt], l, mid, p, x);
else ch[1][q] = insert(ch[1][rt], mid + 1, r, p, x);
if( ch[0][q] ) addedge(q + na + nb, ch[0][q] + na + nb);
if( ch[1][q] ) addedge(q + na + nb, ch[1][q] + na + nb);
}
return q;
}
void get_lr(int ps, int len, int &l, int &r, const int &n) {
int le, ri;
le = rnk[ps], ri = n - 1;
while( le < ri ) {
int mid = (le + ri + 1) >> 1;
if( lcp(rnk[ps], mid) >= len ) le = mid;
else ri = mid - 1;
}
r = le;
le = 0, ri = rnk[ps];
while( le < ri ) {
int mid = (le + ri) >> 1;
if( lcp(rnk[ps], mid) >= len ) ri = mid;
else le = mid + 1;
}
l = ri;
}
long long dp[45*MAXN + 5];
int val[MAXN + 5], que[45*MAXN + 5], s, t;
long long tsort() {
long long ret = 0; s = 1, t = 0;
for(int i=1;i<=ncnt+na+nb;i++) {
if( !ind[i] ) que[++t] = i;
dp[i] = 0;
}
while( s <= t ) {
int f = que[s++];
if( f <= na ) dp[f] += val[f];
ret = max(ret, dp[f]);
for(edge *p=adj[f];p;p=p->nxt) {
ind[p->to]--;
if( !ind[p->to] ) que[++t] = p->to;
dp[p->to] = max(dp[p->to], dp[f]);
}
}
if( t != ncnt + na + nb )
return -1;
else return ret;
}
void solve() {
scanf("%s", S), lenS = strlen(S), ecnt = &edges[0];
get_sa(lenS + 1, 128);
get_height(lenS + 1);
get_st(lenS + 1);
scanf("%d", &na);
for(int i=1;i<=na;i++)
scanf("%d%d", &a[i].l, &a[i].r), a[i].id = i, val[i] = a[i].len();
sort(a + 1, a + na + 1);
scanf("%d", &nb);
for(int i=1;i<=nb;i++)
scanf("%d%d", &b[i].l, &b[i].r), b[i].id = i;
sort(b + 1, b + nb + 1);
int p = na, q = nb; root = ncnt = 0;
while( p >= 1 && q >= 1 ) {
if( a[p].len() >= b[q].len() )
root = insert(root, 1, lenS, rnk[a[p].l-1], a[p].id), p--;
else {
int l, r; get_lr(b[q].l-1, b[q].len(), l, r, lenS + 1);
link_edge(root, 1, lenS, l, r, b[q].id + na), q--;
}
}
while( q >= 1 ) {
int l, r; get_lr(b[q].l-1, b[q].len(), l, r, lenS + 1);
link_edge(root, 1, lenS, l, r, b[q].id + na), q--;
}
scanf("%d", &m);
for(int i=1;i<=m;i++) {
int x, y; scanf("%d%d", &x, &y);
addedge(x, y + na);
}
printf("%lld\n", tsort());
for(int i=1;i<=na+nb+ncnt;i++)
adj[i] = NULL, ind[i] = 0;
}
int main() {
int T; scanf("%d", &T);
while( T-- ) solve();
}
@details@
注意一个区间在线段树中会被拆成 2*logn 个结点而不是 logn 的结点。
虽然都是 O(logn),但是开数组的时候不注意一下就可能会 RE。