BZOJ4212 神牛的养成计划题解

BZOJ4212 神牛的养成计划

Description

给定大小为 \(N\) 的字符串集合 \(S\)\(M\) 组询问,每次给定字符串 \(s_1\)\(s_2\),询问 \(S\) 中有多少串满足其前缀为 \(s_1\) 且其后缀为 \(s_2\)

强制在线,\(N \le 2000\)\(M \le 10^5\)\(\sum \mathrm{len} \le 2 \times 10^6\)

Sol

还是比较神的题目。

首先按照字典序对 \(S\) 排序然后顺次插入一棵字典树,那么易证每个字典树的结点对应一段连续区间。

那么在询问的时候,我们用 \(s_1\) 在字典树上爬到这个点,那么这个点对应的连续区间就是满足 \(s_1\) 是前缀的区间。

但是后缀怎么办呢?

我们考虑把 \(S\) 中所有串的反串都插入一棵可持久化字典树,\(\mathrm{ver}(i)\) 为插入第 \(i\) 个串之后的版本,由于答案满足可减性,那么区间 \([L,R]\) 的答案即为版本 \(R\) 出现次数减去版本 \(L-1\) 出现次数。

至此问题就解决了。时间复杂度 \(\mathrm{len} \log \mathrm{len}\),瓶颈在排序。

Code

调了一晚上,累死咯。

#include<algorithm>
#include<string>
#include<iostream>
#include<utility>
#define pii std::pair<int,int>
#define mp std::make_pair
using std::string;
using std::cin;
using std::cout;
const int M=2e5+5;
const int SIGMA=26;
struct Trie{
	int T[M<<5][SIGMA],lef[M<<5],rig[M<<5],nt;
	void ins(string str,int num){
		int pos=0;
		for(auto ch:str){
			int &p=T[pos][ch-'a'];
			if(!p){
				p=++nt;
				lef[p]=rig[p]=num;
			}
			else{
				lef[p]=std::min(lef[p],num);
				rig[p]=std::max(rig[p],num);
			}
			pos=p;
		}
	}
	pii qry(string str){
		int pos=0;
		for(auto ch:str){
			int p=T[pos][ch-'a'];
			if(!p)return mp(-1,-1);
			pos=p;
		}
		return mp(lef[pos],rig[pos]);
	}
}t;
struct PTrie{
	int T[M<<5][SIGMA],cnt[M<<5],rt[M],nt;
	void ins(int ver,string str,int now){
		int p1=rt[ver],p2=rt[now]=++nt;
		for(auto ch:str){
			T[p2][ch-'a']=++nt;
			for(int i=0;i<SIGMA;++i)if(i!=ch-'a')T[p2][i]=T[p1][i];
			cnt[T[p2][ch-'a']]=cnt[T[p1][ch-'a']]+1;
			p1=T[p1][ch-'a'],p2=T[p2][ch-'a'];
		}
	}
	int qry(string str,int ver){
		int pos=rt[ver];
		for(auto ch:str){
			int p=T[pos][ch-'a'];
			if(!p)return -1;
			pos=p;
		}
		return cnt[pos];
	}
}P;
string str[M];
int n,m;
int main(){
	std::ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;++i)cin>>str[i];
	std::sort(str+1,str+n+1);
	t.lef[0]=1,t.rig[0]=n;
	for(int i=1;i<=n;++i)t.ins(str[i],i),std::reverse(str[i].begin(),str[i].end());
	P.rt[0]=++P.nt;P.cnt[0]=0;
	for(int i=1;i<=n;++i)P.ins(i-1,str[i],i);
	cin>>m;
	int lans=0;
	while(m--){
		string s1,s2;
		cin>>s1>>s2;
		int add=lans%26;
		for(auto &ch:s1){
			if(add+1*ch>'z')ch-=26;
			ch+=add;
		}
		for(auto &ch:s2){
			if(add+1*ch>'z')ch-=26;
			ch+=add;
		}
		std::reverse(s2.begin(),s2.end());
		pii q=t.qry(s1);
		if(q.first<0)lans=0;
		else{
			int ql=P.qry(s2,q.first-1),qr=P.qry(s2,q.second);
			if(qr<0)lans=0;
			else lans=qr-(ql=std::max(ql,0));
		}
		cout<<lans<<'\n';
	}
	return 0;
}
posted @ 2023-04-16 16:05  pokefunc  阅读(158)  评论(0)    收藏  举报