[BZOJ 4212]神牛的养成计划(Trie+可持久化Trie)
[BZOJ 4212]神牛的养成计划(Trie+可持久化Trie)
题面
已知n个字符串,有m个询问(强制在线)。每个询问给出两个字符串\(s_1,s_2\),问\(n\)个字符串中有多少个字符串满足既是\(s_1\)的前缀,又是\(s_2\)的后缀
\(n\)个字符串总长度\(\leq 2 \times 10^6\),\(m \leq 10^5,\sum(|s_1|+|s_2|) \leq 2 \times 10^6\)
分析
先把\(n\)个字符串按照字典序排序并按顺序编号,然后正序插入前缀Trie.在每个节点记录包含这个节点代表的前缀的最小编号和最大编号。
这样,满足是\(s_1\)的前缀的字符串就处于一个连续的区间\([p,q]\)。那么我们再把排序后的\(n\)个字符串倒序插入可持久化Trie,在\([p,q]\)区间上查询与\(s_2\)匹配 的数量即可。
代码
#include<iostream> #include<cstdio> #include<cstring> #include<vector> #include<algorithm> #define maxn 2000 #define maxl 2000000 #define maxs 26 using namespace std; inline void qread(string &s){ s.clear(); char c=getchar(); while(c<'a'||c>'z') c=getchar(); while(c>='a'&&c<='z'){ s.push_back(c); c=getchar(); } } int n,m; string in[maxn+5]; struct Trie { int ch[maxl+5][maxs+1]; int maxt[maxl+5],mint[maxl+5];//子树里字符串的最小和最大编号 int ptr=0; void insert(string &s,int tim) { int n=s.length(); int x=0; for(int i=1; i<=n; i++) { int c=s[i-1]-'a'; if(!ch[x][c]) ch[x][c]=++ptr; x=ch[x][c]; if(!mint[x]) mint[x]=tim; maxt[x]=max(maxt[x],tim); } } int query(string &s){ int n=s.length(); int x=0; for(int i=1; i<=n; i++) { int c=s[i-1]-'a'; if(!ch[x][c]) return 0; x=ch[x][c]; } return x; } } Tpre; struct persist_trie{ int root[maxn+5]; int sz[maxl+5]; int ch[maxl+5][maxs]; int ptr; void insert(int pos,int pre,string &s){ int now=root[pos]=++ptr,last=root[pre]; int n=s.length(); for(int i=n;i>=1;i--){//倒序插入,这样可以匹配后缀 for(int j=0;j<maxs;j++) ch[now][j]=ch[last][j]; int c=s[i-1]-'a'; ch[now][c]=++ptr; now=ch[now][c]; last=ch[last][c]; sz[now]=sz[last]+1; } } int query(int l,int r,string &s){ int now=root[r]; int last=root[l-1]; int n=s.length(); for(int i=n;i>=1;i--){ int c=s[i-1]-'a'; int cnt=sz[ch[now][c]]-sz[ch[last][c]]; if(cnt==0) return 0; now=ch[now][c]; last=ch[last][c]; } return sz[now]-sz[last]; } }Tnex; int main() { #ifdef LOCAL freopen("input.txt","r",stdin); #endif string s1,s2; scanf("%d",&n); for(int i=1;i<=n;i++) qread(in[i]); sort(in+1,in+1+n); // printf("------"); for(int i=1;i<=n;i++){ Tpre.insert(in[i],i); //把字符串按字典序排序后加入,这样一个前缀相同的字符串的编号是连续的 } for(int i=1;i<=n;i++){ Tnex.insert(i,i-1,in[i]);//倒用于匹配后缀 } scanf("%d",&m); int ans=0; for(int i=1;i<=m;i++){ qread(s1); qread(s2); for(int i=0;i<(int)s1.length();i++) s1[i]=(s1[i]-'a'+ans)%26+'a'; for(int i=0;i<(int)s2.length();i++) s2[i]=(s2[i]-'a'+ans)%26+'a'; int x,l,r; x=Tpre.query(s1); if(x){ l=Tpre.mint[x],r=Tpre.maxt[x];//在trie上找出能与s1匹配的字符串的编号区间 ans=Tnex.query(l,r,s2);//在可持久化trie上的对应区间找出后缀匹配个数 }else ans=0; printf("%d\n",ans); } }
版权声明:因为我是蒟蒻,所以请大佬和神犇们不要转载(有坑)的文章,并指出问题,谢谢
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步