• LOJ 3049: 洛谷 P5284: 「十二省联考 2019」字符串问题


    题目传送门:LOJ #3049

    题意简述:

    给定一个长度为 (n) 的母串 (S)

    (n_a) 个 A 类串,都是 (S) 的子串,以区间的形式给出。

    (n_b) 个 B 类串,都是 (S) 的子串,以区间的形式给出。

    (m) 个支配关系,形式为第 (i) 个 A 类串支配第 (j) 个 B 类串。

    你需要求出最长的字符串 (T) 的长度,使得 (T) 可以被划分为若干个 A 类串的拼接,并且相邻两个 A 类串 (t_i)(t_{i+1}) 满足 (t_i) 支配某个 B 类串 (j),而 (j)(t_{i+1}) 的前缀。

    如果 (T) 可以无限长,输出 -1

    题解:

    如果我们设法从每个 B 类串向存在前缀为这个 B 类串的 A 类串连边,则显然若有环则答案可以无限大,若无环则可以做一遍 DAG DP 得到答案。

    然而我们并不能直接实现这一过程,且不论能否快速确定前缀从属关系,这类边的条数就能达到 (mathcal{O}(n_an_b)) 级别。

    当边数太多时,首先应该想到的就是数据结构优化建边,典型的例子是树状结构优化建边,例如线段树和后缀树。

    实际上这题可以直接使用后缀树优化建边,边数只有线性级别,但是过程还是 (mathcal{O}(nlog n)) 的。至于建后缀树可以直接建也可以对反串建 SAM。

    对于我这种不会后缀树,SAM 理解也不深的菜兔,还是选择了后缀数组。

    首先建出后缀数组,这个大家都会。然后对每个 B 类串考虑如何连边。

    假设我们已经对每个 A 类和 B 类串在母串上定位了,那么包含某个 B 类串作为前缀的 A 类串需要满足两个条件:
    该 A 串的左端点对应的后缀和 B 串的左端点对应的后缀的 LCP 长度应该大于 B 串的长度,并且该 A 串的长度也应该大于 B 串的长度。

    第一个条件可以转化为对应着后缀数组上的一段区间,具体根据 Height 数组确定,这一步建出 ST 表后二分可以在 (mathcal{O}(log n)) 的时间内解决。

    第二个条件似乎没有什么巧妙的办法。不过我们可以考虑按照长度从大到小的顺序加入每个串,这样就能够扔掉第二个限制了。

    所以具体的思路就是按照长度从大到小排序插入 A 类串,在 B 类串和数据结构之间建边,使用合适的数据结构维护历史版本和区间。

    很显然,主席树便是合适的数据结构,主席树优化建边,树内边数 (mathcal{O}(n_alog n)),树外边数 (mathcal{O}(n_blog n))

    接下来是代码,复杂度 (mathcal{O}((n+n_a+n_b)log n))

    #include <cstdio>
    #include <vector>
    #include <cstring>
    #include <algorithm>
    
    typedef long long LL;
    const int MN = 200005;
    const int MS = 4200005;
    
    char str[MN];
    int N, Sig, rk[MN], SA[MN], SA2[MN], buk[MN], tmp[MN];
    int Height[MN];
    inline void getHeight() {
    	for (int i = 1, k = 0; i <= N; ++i) {
    		if (rk[i] == 1) { Height[rk[i]] = k = 0; continue; }
    		if (k) --k;
    		int j = SA[rk[i] - 1];
    		while (i + k <= N && j + k <= N && str[i + k] == str[j + k]) ++k;
    		Height[rk[i]] = k;
    	}
    }
    inline void RSort() {
    	for (int i = 1; i <= Sig; ++i) buk[i] = 0;
    	for (int i = 1; i <= N; ++i) ++buk[rk[i]];
    	for (int i = 1; i <= Sig; ++i) buk[i] += buk[i - 1];
    	for (int i = N; i >= 1; --i) SA[buk[rk[SA2[i]]]--] = SA2[i];
    }
    inline void getSA(char *str) {
    	Sig = 127, rk[N + 1] = 0;
    	for (int i = 1; i <= N; ++i) rk[i] = str[i], SA2[i] = i;
    	RSort();
    	for (int j = 1; j <= N; j <<= 1) {
    		int p = 0;
    		for (int i = N - j + 1; i <= N; ++i) SA2[++p] = i;
    		for (int i = 1; i <= N; ++i) if (SA[i] > j) SA2[++p] = SA[i] - j;
    		RSort();
    		tmp[SA[1]] = p = 1;
    		for (int i = 2; i <= N; ++i) {
    			int lst = SA[i - 1], now = SA[i];
    			if (rk[lst] != rk[now] || rk[lst + j] != rk[now + j]) ++p;
    			tmp[SA[i]] = p;
    		}
    		for (int i = 1; i <= N; ++i) rk[i] = tmp[i];
    		if ((Sig = p) == N) break;
    	}
    	getHeight();
    }
    
    int Lg[MN];
    inline void Log(int N) {
    	Lg[0] = -1;
    	for (int i = 1; i <= N; ++i) Lg[i] = Lg[i >> 1] + 1;
    }
    int ST[MN][18];
    inline void InitST() {
    	for (int i = 2; i <= N; ++i) ST[i][0] = Height[i];
    	for (int j = 1; j <= Lg[N - 1]; ++j) {
    		for (int i = 1; i <= 1 << j; ++i) ST[i][j] = 0;
    		for (int i = 1 << j | 1; i <= N; ++i)
    			ST[i][j] = std::min(ST[i - (1 << (j - 1))][j - 1], ST[i][j - 1]);
    	}
    }
    
    int NA, la[MN], ra[MN];
    int NB, lb[MN], rb[MN];
    struct sub {
    	int lb, len, typ, id;
    	sub() {}
    	sub(int lb, int len, int typ, int id) : lb(lb), len(len), typ(typ), id(id) {}
    	inline friend bool operator <(sub i, sub j) {
    		return i.len == j.len ? i.typ < j.typ : i.len > j.len;
    	}
    } substrs[MN * 2];
    
    int d[MS];
    std::vector<int> G[MS];
    inline void addEdge(int x, int y) { ++d[y]; G[x].push_back(y); }
    
    int rt[MN], lc[MS], rc[MS], wgh[MS], cnt;
    void Mdf(int &rt, int l, int r, int p, int x) {
    	lc[++cnt] = lc[rt], rc[cnt] = rc[rt];
    	if (rt) addEdge(cnt, rt);
    	wgh[rt = cnt] = 0;
    	if (l == r) { addEdge(rt, x); return ; }
    	int mid = (l + r) >> 1;
    	if (p <= mid) Mdf(lc[rt], l, mid, p, x), addEdge(rt, lc[rt]);
    	else Mdf(rc[rt], mid + 1, r, p, x), addEdge(rt, rc[rt]);
    }
    void Edg(int rt, int l, int r, int a, int b, int x) {
    	if (!rt || r < a || b < l) return ;
    	if (a <= l && r <= b) { addEdge(x, rt); return ; }
    	int mid = (l + r) >> 1;
    	Edg(lc[rt], l, mid, a, b, x);
    	Edg(rc[rt], mid + 1, r, a, b, x);
    }
    
    int que[MS], l, r;
    LL f[MS];
    
    int main() {
    	int T; scanf("%d", &T);
    	Log(200000);
    	while (T--) {
    		scanf("%s", str + 1);
    		N = strlen(str + 1);
    		getSA(str);
    		InitST();
    		scanf("%d", &NA);
    		for (int i = 1; i <= NA; ++i)
    			scanf("%d%d", &la[i], &ra[i]),
    			substrs[i] = sub(rk[la[i]], ra[i] - la[i] + 1, 0, i);
    		scanf("%d", &NB);
    		for (int i = 1; i <= NB; ++i)
    			scanf("%d%d", &lb[i], &rb[i]),
    			substrs[NA + i] = sub(rk[lb[i]], rb[i] - lb[i] + 1, 1, i);
    		std::sort(substrs + 1, substrs + NA + NB + 1);
    		cnt = NA + NB;
    		for (int i = 1; i <= NA; ++i) wgh[i] = ra[i] - la[i] + 1;
    		for (int i = 1; i <= NB; ++i) wgh[NA + i] = 0;
    		for (int i = 1, gen = 0; i <= NA + NB; ++i) {
    			sub p = substrs[i];
    			if (!p.typ) ++gen, Mdf(rt[gen] = rt[gen - 1], 1, N, p.lb, p.id);
    			else {
    				int Lb = p.lb, Rb = p.lb;
    				for (int j = Lg[p.lb - 1]; ~j; --j)
    					if (ST[Lb][j] >= p.len) Lb -= 1 << j;
    				for (int j = Lg[N - p.lb]; ~j; --j)
    					if (Rb + (1 << j) <= N && ST[Rb + (1 << j)][j] >= p.len) Rb += 1 << j;
    				Edg(rt[gen], 1, N, Lb, Rb, NA + p.id);
    			}
    		}
    		int M; scanf("%d", &M);
    		for (int i, j; M--; ) {
    			scanf("%d%d", &i, &j);
    			addEdge(i, NA + j);
    		}
    		LL Ans = 0;
    		l = 1, r = 0;
    		for (int i = 1; i <= cnt; ++i) {
    			f[i] = wgh[i];
    			if (!d[i]) que[++r] = i;
    		}
    		while (l <= r) {
    			int u = que[l++];
    			Ans = std::max(Ans, f[u]);
    			for (auto v : G[u]) {
    				f[v] = std::max(f[v], f[u] + wgh[v]);
    				if (!--d[v]) que[++r] = v;
    			}
    		}
    		if (r != cnt) puts("-1");
    		else printf("%lld
    ", Ans);
    		for (int i = 1; i <= cnt; ++i) d[i] = 0, G[i].clear();
    	}
    	return 0;
    }
    

    实际上,同样是后缀数组预处理,关于建边有更好的方法,比如用后缀数组建出后缀树,然后在后缀树上乱搞建边,这样是线性的。

  • 相关阅读:
    C++学习笔记-C++对C语言的扩充和增强
    C++学习笔记-C++与C语言的一些区别
    C++学习笔记-C++与C语言的一些区别
    C学习笔记-字符串处理函数
    C学习笔记-字符串处理函数
    C学习笔记-gdb
    深入理解C语言-函数指针
    深入理解C语言-函数指针
    深入理解C语言-结构体做函数参数
    async 珠峰培训node正式课笔记 【async】任务流程控制,异步流程控制
  • 原文地址:https://www.cnblogs.com/PinkRabbit/p/SHOI2019D1T2.html
Copyright © 2020-2023  润新知