CF666E Forensic Examination 广义SAM、线段树合并、倍增、扫描线

传送门


朴素想法:对\(M\)个匹配串\(T_1,...,T_M\)建立广义SAM,对于每一次询问,找到这个SAM上\(S[pl...pr]\)对应的状态,然后计算出对于每一个\(i \in [l,r]\),计算出\(T_i\)能够转移到这个状态的次数然后取\(max\)

需要解决两个问题:

1、如何快速找到\(S[pl...pr]\)在SAM上对应的状态。

因为题目没有给\(\sum pr - pl\)的条件,所以不能暴力,考虑一个一个字符加进去,每加入一个字符计算对应答案。将询问按照\(pr\)从小到大排序然后扫描线:把模板串放在广义SAM上匹配。设插入第\(i\)个字符时到达的状态为\(u\)、匹配长度为\(len\),那么对于某个询问\(S[x...i]\),若\(len < x - i + 1\)则次数为\(0\)

\(len \geq x - i + 1\)时不代表可以在\(u\)上计算答案,可能\(u\)状态包含的串中不包含\(S[x...i]\)。此时还要跳\(father\)直到\(Shortest_u \leq x - i + 1\),状态\(u\)才是需要计算的状态。这里可以倍增优化跳的过程。

2、如何快速计算\(T_i\)转移到状态\(u\)的次数。

可以发现这个跟计算\(endpos\)集合有些类似,即如果某个状态\(u\)被标记为“串\(T_i\)可以走到”,那么它的祖先也一定会有这个标记。故每个节点维护一棵线段树记录每一个串在该状态的标记次数,使用线段树合并把\(parent\)树上所有节点的线段树处理出来,就可以每一次\(O(logn)\)地询问。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<cctype>
#include<vector>
#include<algorithm>
#include<cstring>
#include<queue>
#include<iomanip>
#define PII pair < int , int >
#define st first
#define nd second
//This code is written by Itst
using namespace std;

const int MAXN = 5e5 + 7 , MAXL = 1e5 + 7;
char s[MAXL] , S[MAXN];
int M , Q , cnt = 1 , ch[MAXL][26] , dep[MAXL] , ind[MAXL];
vector < int > bel[MAXL];
struct Query{
	int a , b , c , d , ind;
	bool operator <(const Query a)const{return d < a.d;}
};
vector < Query > que;
PII ans[MAXN];

namespace Tree{
	struct node{
		int l , r , maxN , sz , ind;
	}Tree[MAXL * 60];
	int rt[MAXL] , cnt = 1;
	
#define lch (Tree[x].l)
#define rch (Tree[x].r)
#define mid ((l + r) >> 1)
	inline void pushup(int x){
		if(Tree[rch].maxN > Tree[lch].maxN){
			Tree[x].maxN = Tree[rch].maxN;
			Tree[x].ind = Tree[rch].ind;
		}
		else{
			Tree[x].maxN = Tree[lch].maxN;
			Tree[x].ind = Tree[lch].ind;
		}
	}
	
	int insert(int x , int l , int r , int tar){
		if(!x) x = ++cnt;
		++Tree[x].sz;
		if(l == r){
			Tree[x].maxN = Tree[x].sz;
			Tree[x].ind = l;
			return x;
		}
		if(mid >= tar) lch = insert(lch , l , mid , tar);
		else rch = insert(rch , mid + 1 , r , tar);
		pushup(x);
		return x;
	}

	PII query(int x , int l , int r , int L , int R){
		if(l >= L && r <= R)
			return PII(Tree[x].maxN , -Tree[x].ind);
		PII sum(0 , 0);
		if(mid >= L)
			sum = max(sum , query(lch , l , mid , L , R));
		if(mid < R)
			sum = max(sum , query(rch , mid + 1 , r , L , R));
		return sum;
	}

	int merge(int p , int q , int l , int r){
		if(!p || !q) return p + q;
		int t = ++cnt;
		Tree[t].sz = Tree[p].sz + Tree[q].sz;
		if(l == r){
			Tree[t].maxN = Tree[t].sz;
			Tree[t].ind = l;
		}
		else{
			Tree[t].l = merge(Tree[p].l , Tree[q].l , l , mid);
			Tree[t].r = merge(Tree[p].r , Tree[q].r , mid + 1 , r);
			pushup(t);
		}
		return t;
	}
}

namespace SAM{
    int Lst[MAXL] , Sst[MAXL] , fa[MAXL] , trans[MAXL][26];
    int cnt = 1;
	
    int insert(int p , int len , int x){
        int t = ++cnt; Lst[t] = len;
        while(p && !trans[p][x]){
            trans[p][x] = t;
            p = fa[p];
        }
        if(!p){
            Sst[t] = fa[t] = 1;
            return t;
        }
        int q = trans[p][x];
        Sst[t] = Lst[p] + 2;
        if(Lst[q] == Lst[p] + 1){
            fa[t] = q;
            return t;
        }
        int k = ++cnt;
        memcpy(trans[k] , trans[q] , sizeof(trans[k]));
        Lst[k] = Lst[p] + 1; Sst[k] = Sst[q];
        Sst[q] = Lst[p] + 2;
        fa[k] = fa[q]; fa[q] = fa[t] = k;
        while(trans[p][x] == q){
            trans[p][x] = k;
            p = fa[p];
        }
		return t;
    }

	vector < int > ch[MAXL] , bel[MAXL];
	int jump[MAXL][20];
	
	void dfs(int x){
		for(auto &t : bel[x])
			Tree::rt[x] = Tree::insert(Tree::rt[x] , 1 , M , t);
		for(int i = 1 ; i <= 18 ; ++i)
			jump[x][i] = jump[jump[x][i - 1]][i - 1];
		
		for(auto &t : ch[x]){
			jump[t][0] = x;
			dfs(t);
			Tree::rt[x] = Tree::merge(Tree::rt[x] , Tree::rt[t] , 1 , M);
		}
	}
	
	void build(){
		for(int i = 2 ; i <= cnt ; ++i)
			ch[fa[i]].push_back(i);
		dfs(1);
	}

	int calc(int x , int l){
		if(Sst[x] <= l) return x;
		for(int i = 18 ; i >= 0 ; --i)
			if(Sst[jump[x][i]] > l)
				x = jump[x][i];
		return jump[x][0];
	}
}

void insert(int ind){
	scanf("%s" , s + 1);
	int L = strlen(s + 1) , cur = 1;
	for(int i = 1 ; i <= L ; ++i){
		if(!ch[cur][s[i] - 'a'])
			dep[ch[cur][s[i] - 'a'] = ++cnt] = dep[cur] + 1;
		bel[cur = ch[cur][s[i] - 'a']].push_back(ind);
	}
}

void build(){
	queue < int > q;
	q.push(ind[1] = 1);
	while(!q.empty()){
		int t = q.front();
		q.pop();
		SAM::bel[ind[t]] = bel[t];
		for(int i = 0 ; i < 26 ; ++i)
			if(ch[t][i]){
				ind[ch[t][i]] = SAM::insert(ind[t] , dep[ch[t][i]] , i);
				q.push(ch[t][i]);
			}
	}
	SAM::build();
}

int main(){
    #ifndef ONLINE_JUDGE
    freopen("in" , "r" , stdin);
    //freopen("out" , "w" , stdout);
    #endif
	scanf("%s %d" , S + 1 , &M);
	for(int i = 1 ; i <= M ; ++i)
		insert(i);
	build();
	scanf("%d" , &Q);
	for(int i = 1 ; i <= Q ; ++i){
		Query now;
		scanf("%d %d %d %d" , &now.a , &now.b , &now.c , &now.d);
		now.ind = i;
		que.push_back(now);
	}
	sort(que.begin() , que.end());
	int bef = 0 , u = 1 , len = 0;
	for(auto &q : que){
		while(bef < q.d){
			++bef;
			while(u != 1 && !SAM::trans[u][S[bef] - 'a'])
				len = SAM::Lst[u = SAM::fa[u]];
			if(SAM::trans[u][S[bef] - 'a']){
				++len;
				u = SAM::trans[u][S[bef] - 'a'];
			}
		}
		if(len >= q.d - q.c + 1){
			int t = SAM::calc(u , q.d - q.c + 1);
			ans[q.ind] = Tree::query(Tree::rt[t] , 1 , M , q.a , q.b);
		}
		if(!ans[q.ind].st)
			ans[q.ind].nd = -q.a;
	}
	for(int i = 1 ; i <= Q ; ++i)
		printf("%d %d\n" , -ans[i].nd , ans[i].st);
	return 0;
}
posted @ 2019-02-27 10:59  cjoier_Itst  阅读(286)  评论(0编辑  收藏  举报