1. 程式人生 > >【codeforces666E】Forensic Examination 廣義後綴自動機+樹上倍增+線段樹合並

【codeforces666E】Forensic Examination 廣義後綴自動機+樹上倍增+線段樹合並

first == sum cpp class ads ble urn ack

題目描述

給出 $S$ 串和 $m$ 個 $T_i$ 串,$q$ 次詢問,每次詢問給出 $l$ 、$r$ 、$x$ 、$y$ ,求 $S_{x...y}$ 在 $T_l,T_{l+1},...,T_r$ 中的哪一個裏出現次數最多,輸出出現次數最多的串編號(如果有多個則輸出編號最小的)以及相應出現次數。

$|S|,q\le 5\times 10^5$ ,$\sum\limits_{i=1}^m|T_i|\le 5\times 10^4$ 。


題解

廣義後綴自動機+樹上倍增+線段樹合並

對 $S$ 串和所有 $T_i$ 串的反串放到一起建立廣義後綴自動機,得到廣義後綴樹。

考慮 $S$ 串的 $l...r$ 部分在 $T_i$ 串的出現次數體現為什麽:" $S$ 串的 $l...r$ 部分" 在後綴Trie上體現為:順著 $S$ 的以 $l$ 開頭的後綴走到 $S_{l...r}$ 對應節點,該節點是子樹內所有後綴的前綴。因此統計的就是該節點子樹內有多少個 $T_i$ 的後綴節點。

而現在給出的是後綴樹,後綴樹相比後綴Trie對無用節點進行壓縮,有可能 $S_{l...r}$ 是無用節點。因此要找到的是:最小的 $i\ge r$ ,使得 $S_{l...i}$ 是非無用節點。使用倍增,從底到上求出最靠近根節點的 $dis\ge r-l+1$ 的節點。

問題轉化為:求一個點的子樹中出現次數最多的顏色是什麽。

將詢問離線,使用線段樹維護子樹(right集合)中每種顏色出現的次數,維護區間最大值即最大值位置。DFS整棵樹,遞歸子樹後進行線段樹合並,最後處理該點對應的詢問。

時間復雜度 $O(26n+n\log n)$ 。

#include <vector>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#define N 1100000
#define lson l , mid , ls[x]
#define rson mid + 1 , r , rs[x]
using namespace std;
typedef pair<int , int> pr;
vector<int> vq[N];
int m , pos[N] , c[N][26] , dis[N] , pre[N] , tot = 1 , last = 1 , head[N] , to[N] , next[N] , cnt , fa[N][22] , deep[N] , log[N] , ls[N * 5] , rs[N * 5] , root[N] , tp , ql[N] , qr[N];
pr mx[N * 5] , ans[N];
char str[N];
void extend(int x)
{
	int p = last;
	if(c[p][x])
	{
		int q = c[p][x];
		if(dis[q] == dis[p] + 1) last = q;
		else
		{
			int nq = ++tot;
			memcpy(c[nq] , c[q] , sizeof(c[q]));
			dis[nq] = dis[p] + 1 , pre[nq] = pre[q] , last = pre[q] = nq;
			while(p && c[p][x] == q) c[p][x] = nq , p = pre[p];
		}
	}
	else
	{
		int np = last = ++tot;
		dis[np] = dis[p] + 1;
		while(p && !c[p][x]) c[p][x] = np , p = pre[p];
		if(!p) pre[np] = 1;
		else
		{
			int q = c[p][x];
			if(dis[q] == dis[p] + 1) pre[np] = q;
			else
			{
				int nq = ++tot;
				memcpy(c[nq] , c[q] , sizeof(c[q]));
				dis[nq] = dis[p] + 1 , pre[nq] = pre[q] , pre[np] = pre[q] = nq;
				while(p && c[p][x] == q) c[p][x] = nq , p = pre[p];
			}
		}
	}
}
inline void add(int x , int y)
{
	to[++cnt] = y , next[cnt] = head[x] , head[x] = cnt;
}
void dfs(int x)
{
	int i;
	for(i = 1 ; i <= log[deep[x]] ; i ++ ) fa[x][i] = fa[fa[x][i - 1]][i - 1];
	for(i = head[x] ; i ; i = next[i]) fa[to[i]][0] = x , deep[to[i]] = deep[x] + 1 , dfs(to[i]);
}
int find(int x , int d)
{
	int i;
	for(i = log[deep[x]] ; ~i ; i -- )
		if((1 << i) <= deep[x] && dis[fa[x][i]] >= d)
			x = fa[x][i];
	return x;
}
inline void pushup(int x)
{
	mx[x] = max(mx[ls[x]] , mx[rs[x]]);
}
void insert(int p , int l , int r , int &x)
{
	if(!x) x = ++tp;
	if(l == r)
	{
		mx[x].first ++ , mx[x].second = -p;
		return;
	}
	int mid = (l + r) >> 1;
	if(p <= mid) insert(p , lson);
	else insert(p , rson);
	pushup(x);
}
int merge(int l , int r , int x , int y)
{
	if(!x) return y;
	if(!y) return x;
	if(l == r)
	{
		mx[x].first += mx[y].first;
		return x;
	}
	int mid = (l + r) >> 1;
	ls[x] = merge(l , mid , ls[x] , ls[y]);
	rs[x] = merge(mid + 1 , r , rs[x] , rs[y]);
	pushup(x);
	return x;
}
pr query(int b , int e , int l , int r , int x)
{
	if(b <= l && r <= e) return mx[x];
	int mid = (l + r) >> 1;
	if(e <= mid) return query(b , e , lson);
	else if(b > mid) return query(b , e , rson);
	else return max(query(b , e , lson) , query(b , e , rson));
}
void solve(int x)
{
	int i;
	for(i = head[x] ; i ; i = next[i]) solve(to[i]) , root[x] = merge(1 , m , root[x] , root[to[i]]);
	for(i = 0 ; i < (int)vq[x].size() ; i ++ ) ans[vq[x][i]] = query(ql[vq[x][i]] , qr[vq[x][i]] , 1 , m , root[x]);
}
inline char nc()
{
	static char buf[100000] , *p1 , *p2;
	return p1 == p2 && (p2 = (p1 = buf) + fread(buf , 1 , 100000 , stdin) , p1 == p2) ? EOF : *p1 ++ ;
}
inline int readnum()
{
	int ret = 0; char ch = nc();
	while(!isdigit(ch)) ch = nc();
	while(isdigit(ch)) ret = ((ret + (ret << 2)) << 1) + (ch ^ ‘0‘) , ch = nc();
	return ret;
}
inline int readstr(char *p)
{
	char ch = nc() , *q = p;
	while(isalpha(ch)) *q ++ = ch , ch = nc();
	return q - p;
}
char pbuf[10000000] , *pp = pbuf;
inline void write(int x)
{
	static int sta[20];
	int top = 0;
	if(!x) sta[top ++ ] = 0;
	while(x) sta[top ++ ] = x % 10 , x /= 10;
	while(top -- ) *pp ++ = sta[top] ^ ‘0‘;
}
int main()
{
	int q , i , j , x , y;
	for(i = readstr(str + 1) ; i ; i -- ) extend(str[i] - ‘a‘) , pos[i] = last;
	m = readnum();
	for(i = 1 ; i <= m ; i ++ )
	{
		last = 1;
		for(j = readstr(str + 1) ; j ; j -- )
			extend(str[j] - ‘a‘) , insert(i , 1 , m , root[last]);
	}
	for(i = 2 ; i <= tot ; i ++ ) add(pre[i] , i) , log[i] = log[i >> 1] + 1;
	dfs(1);
	q = readnum();
	for(i = 1 ; i <= q ; i ++ )
	{
		ql[i] = readnum() , qr[i] = readnum() , x = readnum() , y = readnum();
		vq[find(pos[x] , y - x + 1)].push_back(i);
	}
	solve(1);
	for(i = 1 ; i <= q ; i ++ ) write(ans[i].first ? -ans[i].second : ql[i]) , *pp ++ = ‘ ‘ , write(ans[i].first) , *pp ++ = ‘\n‘;
	fwrite(pbuf , 1 , pp - pbuf , stdout);
	return 0;
}

【codeforces666E】Forensic Examination 廣義後綴自動機+樹上倍增+線段樹合並