[NOI2011]阿狸的打字机
这题可以大大加深我们对自动AC机的理解。
一上来:哇,这是什么神仙读入啊,如果按照它的要求一个一个把所有串建出来的话,肯定会MLE呀!
后来想一想,它这么读入,肯定构成一棵树,并且,它刚好是字典树!
这里是建树的方法。
void ins(){
int x=1;
for(int i=0;i<S;i++){
if(s[i]=='P')t[x].fin.push_back(++n),pos[n]=x;
else if(s[i]=='B'){
if(x==1)continue;
x=t[x].fa;
}else{
if(!t[x].ch[s[i]-'a'])t[x].ch[s[i]-'a']=++cnt,t[cnt].fa=x,t[x].org[s[i]-'a']=true;
x=t[x].ch[s[i]-'a'];
}
}
}
注意到这里有一个将串映射到trie中节点的数组,以后有用。
然后就不会了,看了题解。
我们必须明确一点,就是一个节点的所有祖先内,包含了它的全部出现过的前缀。
因此,对于询问,我们找到串的结尾节点,则该节点在树中的子树内,有多少个节点出现在了串中,则询问的答案就是多少。
因为对于每一个这样的节点,它总可以通过多次跳跳到串的结尾节点上。
但是,就算这样,我们也无法非常便捷地求答案。
考虑转换思路,以串为枚举对象,查询有多少节点在串的结尾节点的子树内。
查询子树内信息,我们有经典结构dfs序。
我们可以将询问离线下来,并将询问存储在节点的位置上。然后在trie树上爆搜(注意是trie树,不是trie图,在求时连上的新点不能进入,不然会挂),每到一个节点就统计(以当前节点到根所表示的字符串)作为的答案。
在dfs序上单点修改,区间求和,我们有树状数组。
关键代码(爆炸警告):
for(int j=0;j<v[t[x].fin[i]].size();j++)
res[v[t[x].fin[i]][j].second]=
ask(t[pos[v[t[x].fin[i]][j].first]].dfn+t[pos[v[t[x].fin[i]][j].first]].sz-1)-
ask(t[pos[v[t[x].fin[i]][j].first]].dfn-1);
是一个,存的是询问,其中是串,是序号。
是统计答案的数组。
是树状数组的前缀和。
是dfs序。
是树中子树大小。
注意哪里是树,哪里是trie树,哪里是trie图!!
代码:
#include<bits/stdc++.h>
using namespace std;
int n,m,S,cnt=1,head[100100],lim,pos[100100],BIT[100100],res[100100];
char s[100100];
struct Edge{
int to,next;
}edge[200100];
void ae(int u,int v){
static int tot;
edge[tot].next=head[u],edge[tot].to=v,head[u]=tot++;
}
void add(int x,int val){
while(x<=lim)BIT[x]+=val,x+=x&-x;
}
int ask(int x){
int sum=0;
while(x)sum+=BIT[x],x-=x&-x;
return sum;
}
struct AC_automaton{
int ch[26],fa,fail,dfn,sz;
bool org[26];
vector<int>fin;
}t[100100];
void ins(){
int x=1;
for(int i=0;i<S;i++){
if(s[i]=='P')t[x].fin.push_back(++n),pos[n]=x;
else if(s[i]=='B'){
if(x==1)continue;
x=t[x].fa;
}else{
if(!t[x].ch[s[i]-'a'])t[x].ch[s[i]-'a']=++cnt,t[cnt].fa=x,t[x].org[s[i]-'a']=true;
x=t[x].ch[s[i]-'a'];
}
}
}
vector<pair<int,int> >v[100100];
queue<int>q;
void build(){
for(int i=0;i<26;i++){
if(!t[1].ch[i])t[1].ch[i]=1;
else t[t[1].ch[i]].fail=1,q.push(t[1].ch[i]),ae(1,t[1].ch[i]);
}
while(!q.empty()){
int x=q.front();q.pop();
for(int i=0;i<26;i++){
if(t[x].ch[i])t[t[x].ch[i]].fail=t[t[x].fail].ch[i],q.push(t[x].ch[i]),ae(t[t[x].fail].ch[i],t[x].ch[i]);
else t[x].ch[i]=t[t[x].fail].ch[i];
}
}
}
void getdfn(int x){
// printf("%d\n",x);
t[x].dfn=++lim,t[x].sz=1;
for(int i=head[x];i!=-1;i=edge[i].next)getdfn(edge[i].to),t[x].sz+=t[edge[i].to].sz;
// printf("%d\n",x);
}
void dfs(int x){
add(t[x].dfn,1);
for(int i=0;i<t[x].fin.size();i++){
// printf("%d:%d\n",x,t[x].fin[i]);
for(int j=0;j<v[t[x].fin[i]].size();j++)res[v[t[x].fin[i]][j].second]=ask(t[pos[v[t[x].fin[i]][j].first]].dfn+t[pos[v[t[x].fin[i]][j].first]].sz-1)-ask(t[pos[v[t[x].fin[i]][j].first]].dfn-1);
}
for(int i=0;i<26;i++)if(t[x].org[i])dfs(t[x].ch[i]);
add(t[x].dfn,-1);
}
int main(){
scanf("%s",s),S=strlen(s),memset(head,-1,sizeof(head)),ins(),build(),getdfn(1);
scanf("%d",&m);
for(int i=1,x,y;i<=m;i++)scanf("%d%d",&x,&y),v[y].push_back(make_pair(x,i));
dfs(1);
for(int i=1;i<=m;i++)printf("%d\n",res[i]);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?