• @loj



    @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
    ", 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
    ", 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。

  • 相关阅读:
    jquery对同级的td做radio限制
    "javascript:void(0)"用法
    SQL 插入查询的最大ID 号 进行批量
    Java数字、货币值和百分数等的格式化处理
    PHP 注意问题
    Android Fragment真正意义上的onResume和onPause
    Android_CodeWiki_03
    Android_CodeWiki_02
    Android_CodeWiki_01
    Android 启动APP黑屏解决方案
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11361712.html
Copyright © 2020-2023  润新知