[2018ICPC焦作] K Counting Failures on a Trie 哈希+倍增

给出一个 n ≤ 1 e 5 n\leq1e5 n1e5的字典树,并且给出 n ≤ 1 e 5 n\leq1e5 n1e5的字符串 s s s,每次询问给出一个区间 [ l , r ] [l,r] [l,r],然后让这个子串在字典树上沿边匹配。每次如果失配就会重新回到根的位置重新匹配。对于每次查询,求出失配的次数以及最终匹配到位置。
先考虑如何快速求出第一次失配的位置,对于每个后缀的开头,一定满足当前这个前缀如果不失配,那么更小的前缀也不会失配。所以可以二分前缀的长度。可以预处理dfs一遍字典树,用一个map存下来所有前缀值的哈希。这样最长的满足的前缀再 + 1 +1 +1就是最小的失配位置。
同样的方法可以处理出每个后缀作为开头失配的位置。 f i , 0 f_{i,0} fi,0表示 i i i位置开始失配 1 1 1次的位置。然后倍增预处理出 f i , j f_{i,j} fi,j表示 i i i开头失配 j j j次的位置。
对于每次查询直接倍增,最后的一段一定是一个前缀,用map保存点的信息。这样就做到单次询问 O ( l o g n ) O(logn) O(logn)了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int inf=0x3f3f3f3f;
const int N=1e5+3;

char s[N];

struct edge { int v;char w;};
vector<edge> go[N]; 
unordered_map<ull,int> mp; 


const int base=163;
unsigned long long h[N],p[N];
unsigned long long gethash(int l,int r) { return h[r]-h[l]*p[r-l]; }
void init() { 
	p[0]=1;
	for(int i=1;i<=100000;i++)
		p[i]=p[i-1]*base;
}

void dfs(int u,ull hs) {
	mp[hs]=u;
	for(auto &e:go[u]) {
		int v=e.v;
		dfs(v,hs*base+e.w);
	}
}

int f[N][22];

int main() { 
	init();
	int T;
	scanf("%d",&T); 
	while(T--) {
		int n,m,q;
		mp.clear();
		scanf("%d%d%d",&n,&m,&q);
		for(int i=0;i<=n;i++)
			go[i].clear();
		for(int i=1;i<=n;i++) {
			int fa;char w[3];
			scanf("%d%s",&fa,w);
			go[fa].push_back({i,w[0]});
		}
		scanf("%s",s+1);
		for(int i=1;i<=m;i++)
			h[i]=h[i-1]*base+s[i];
		int lg=0;
		while((1<<lg)<=m) {
			lg++;
		}
		dfs(0,0); 
		for(int i=0;i<=m;i++) {
			int l=0,r=m-i;
			int ans=0;
			while(l<=r) {
				int mid=(l+r)>>1;
				if(mp.count(gethash(i,i+mid))) ans=mid,l=mid+1;
				else r=mid-1;
			}
			f[i][0]=i+ans+1;
		}
		f[m+1][0]=m+1;
		for(int i=1;i<lg;i++) {
			for(int j=0;j<=m+1;j++) {
				f[j][i]=f[f[j][i-1]][i-1];
			}
		}
		while(q--) {
			int l,r;
			scanf("%d%d",&l,&r); 
			int ans=0;l=l-1;
			for(int i=lg-1;i>=0;i--) {
				if(f[l][i]<=r) {
					l=f[l][i];
					ans+=(1<<i);
				}
			}
			int id=mp[gethash(l,r)];
			printf("%d %d\n",ans,id);
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值