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;
}