zoukankan      html  css  js  c++  java
  • 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
    ", res.se, res.fi);
    	}
    	return 0;
    }
    
  • 相关阅读:
    我告诉你 电脑软件工具
    mysql 查询当天、本周,本月,上一个月的数据
    springboot:springboot+mybatis多数据源最简解决方案
    MySQL数据库优化的(经典必看)
    MyBatis 的强大特性之一便是它的动态 SQL之常用属性使用
    你知道Spring 中使用了哪些设计模式?
    kafka 相关面试问题
    掌握TCP/IP的通信原理(三次握手、四次挥手)。
    jsp和servlet实现文件的上传和下载
    Java获取数据库记录通过javabean映射,并存入list集合
  • 原文地址:https://www.cnblogs.com/PCCCCC/p/13435997.html
Copyright © 2011-2022 走看看