Codeforces 666E Forensic Examination (后缀自动机 + 线段树合并)

题解:首先广义 \(SAM\) , 然后分析,每次查询需要查的字串是确定,并且给了你右端点,所以我们在插入文本串 (一开始给的字符串) 时,记录一下第 \(i\) 个字符插入时所对应的节点,然后我们根据后缀链接往上爬,直到爬到一个节点 \(p\) 满足的 \(minLen[p] \leq p_r - p_l + 1 \leq maxLen[p]\) ,这个节点就包含了我们要查的字串,这个过程可以用倍增实现,复杂度为 \(log(n)\) ,此外,我们还要知道每个节点中每个模式串的贡献,所以我们对每个节点建立一颗线段树,记录每个模式串有多少个属于该节点的 \(endpoint\) ,每个节点包含的某模式串的 \(endpoint\) 个数可以 \(dfs\) 一遍 \(link\) 树得出来,但是这样不仅是空间还是时间都不够用,所以考虑线段树合并。具体看代码。

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
typedef long long LL;
typedef pair<int, int> pii;
const int MAXE = 2e6 + 50;
int n, m;
struct Edge
{
	int to, next;
} edge[MAXE * 2];
int k, head[MAXE];
void add(int a, int b){
	edge[k].to = b;
	edge[k].next = head[a];
	head[a] = k++;
}
struct segementTree
{
	static const int maxn = 3e6 + 60;
	int tree[maxn << 2], ls[maxn << 2], rs[maxn << 2], val[maxn << 2];
	int root[maxn << 2];
	int sz = 0;
	void init() {sz = 0;};
	void PushUp(int rt){ // 向上合并
		if(tree[ls[rt]] == tree[rs[rt]]){
			tree[rt] = tree[ls[rt]];
			val[rt] = min(val[ls[rt]], val[rs[rt]]);
		} else if(tree[ls[rt]] > tree[rs[rt]]){
			tree[rt] = tree[ls[rt]];
			val[rt] = val[ls[rt]];
		} else {
			tree[rt] = tree[rs[rt]];
			val[rt] = val[rs[rt]];
		}
	}
	void insert(int le, int ri, int pos, int &rt){ // 模式串pos在该节点的贡献 + 1
		if(!rt) rt = ++sz;
		if(le == ri) {
			tree[rt]++;
			val[rt] = le;
			return;
		}
		int mid = (le + ri) >> 1;
		if(pos <= mid) insert(le, mid, pos, ls[rt]);
		else insert(mid + 1, ri, pos, rs[rt]);
		PushUp(rt);
	}
 
	int merge(int le, int ri, int u, int v){ // 线段树合并, 这里选择新开一个节点,而不是覆盖,因为空间允许,不需要将查询离线
		if(!u || !v) return u | v;
		int now = ++sz;
		if(le == ri){
			tree[now] = tree[u] + tree[v];
			val[now] = le;
			return now;
		}
		int mid = (le + ri) >> 1;
		ls[now] = merge(le, mid, ls[u], ls[v]);
		rs[now] = merge(mid + 1, ri, rs[u], rs[v]);
		PushUp(now);
		return now;
	}
 
	void dfs(int u, int pre){
		for(int i = head[u]; i != -1; i = edge[i].next){
			int to = edge[i].to;
			if(to == pre) continue;
			dfs(to, u);
			root[u] = merge(1, m, root[u], root[to]);
		}
	}
 
	pii Query(int le, int ri, int L, int R, int rt){
		if(L <= le && ri <= R){
			return pii{tree[rt], val[rt]};
		}
		int mid = (le + ri) >> 1;
		pii ans = {0, 0};
		if(L <= mid) {
			pii res =  Query(le, mid, L, R, ls[rt]);
			if(ans.fi < res.fi) ans = res;
		} 
		if(R > mid){
			pii res = Query(mid + 1, ri, L, R, rs[rt]);
			if(ans.fi < res.fi) ans = res;
		}
		return ans;
	} 
} seTree;
struct ex_SAM
{
	static const int maxn = 2e6 + 60;  
	int nex[maxn][26], len[maxn], link[maxn], pos[maxn];
	int tot, char_num = 26, custr = 0;
	void init() {tot = 1, link[0] = -1;}
	int insert_SAM(int last, int c){
		int cur = nex[last][c], p = link[last];
		len[cur] = len[last] + 1;
		while(p != -1){
			if(!nex[p][c]) nex[p][c] = cur;
			else break;
			p = link[p];
		}
		if(p == -1) {
			link[cur] = 0; return cur;
		}
		int q = nex[p][c];
		if(len[p] + 1 == len[q]) {link[cur] = q; return cur;}
		int clone = tot++;
		for(int i = 0; i < char_num; i++){
			nex[clone][i] = len[nex[q][i]] != 0 ? nex[q][i] : 0;
		}
		len[clone] = len[p] + 1;
		while(p != -1 && nex[p][c] == q){
			nex[p][c] = clone, p = link[p];
		}
		link[clone] = link[q], link[cur] = clone, link[q] = clone;
		return cur;
	}
 
	int insertTire(int cur, int c){ // 先建立字典树
		if(!nex[cur][c]) nex[cur][c] = tot++;
		return nex[cur][c];
	}
	void insert(string s){
		custr++;
		int root = 0;
		for(auto ch : s) {
			root = insertTire(root, ch - 'a');
			seTree.insert(1, m, custr, seTree.root[root]); // 该节点模式串custr的endpoint数量 + 1
		}
		
	}
 
	void insert2(string s){ // 文本串插入字典树
		int root = 0;
		int sz = s.size();
		for(int i = 0; i < sz; i++) {
			root = insertTire(root, s[i] - 'a');
			pos[i] = root; // 记录文本串每个endpoint对应的最初的节点(也就是最深的拥有以 i 为结尾的点)
		}
	}
	void Build(){ // 建立后缀自动机
		queue<pii> que;
		for(int i = 0; i < char_num; i++){
			if(nex[0][i]) que.push({i, 0});
		}
		while(que.size()){
			auto item = que.front();
			que.pop();
			auto last = insert_SAM(item.se, item.fi);
			for(int i = 0; i < char_num; i++){
				if(nex[last][i]) que.push({i, last});
			}
		}
	}
 
	void BuildTree(){ // 建立link树
		for(int i = 0; i < tot; i++) head[i] = -1;
		for(int i = 1; i < tot; i++){
			add(i, link[i]);
			add(link[i], i);
		}
	}
 
	int fa[maxn][30], depth[maxn];
	void dfs(int u, int pre, int d){
		fa[u][0] = pre, depth[u] = d;
		for(int i = head[u]; i != -1; i = edge[i].next){
			int to = edge[i].to;
			if(to == pre) continue;
			dfs(to, u, d + 1);
		}
	}
	void init(int root){ // 倍增处理lca
		dfs(root, -1, 0);
		for(int j = 0; (1 << (j + 1)) < tot - 1; j++){
			for(int i = 1; i <= tot - 1; i++){
				if(fa[i][j] < 0) fa[i][j + 1] = -1;
				else fa[i][j + 1] = fa[fa[i][j]][j];
			}
		}
	}
 
	int Find(int x, int nlen){
		int u = pos[x];
		for(int i = 20; i >= 0; i--){
			if(fa[u][i] != -1 && len[fa[u][i]] >= nlen) {
				u = fa[u][i];
			}
		}
		return u;
	}
 
	
} exSam;
 
string s, t;
int main(int argc, char const *argv[])
{
	cin >> s;
	exSam.init();
	exSam.insert2(s);
	cin >> m;
	for(int i = 1; i <= m; i++){
		cin >> s;
		exSam.insert(s);
	}
	exSam.Build();
	exSam.BuildTree();
	exSam.init(0);
	seTree.dfs(0, -1);

	cin >> n;
	while(n--){
		int le, ri, L, R;
		scanf("%d%d%d%d", &le, &ri, &L, &R);
		L--, R--; 
		int len = R - L + 1;
		int u = exSam.Find(R, len);
		pii res = seTree.Query(1, m, le, ri, seTree.root[u]);
		if(res.se == 0) res.se = le;// 坑点,所选模式串不存在该字串,则要输出le
		printf("%d %d\n", res.se, res.fi);
	}
	return 0;
}
posted @ 2020-08-04 21:20  从小学  阅读(143)  评论(0编辑  收藏  举报