[ICPC2019 WF]First of Her Name
X.[ICPC2019 WF]First of Her Name
这题的一种解法是把所有东西(名字串和翻转的询问串)建出一棵树来,然后跑树上后缀排序,用二分+哈希求出数组,然后使用单调栈找出每个位置最多能够向左向右延伸多远。但是按照某人的说法出题人好像出了卡此算法的数据,所以最后WA掉了。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=2001000;
const int NN=2000000;
typedef unsigned long long ull;
ull sd1=31,sd2=43,iv1=17256631552825064415ull,iv2=9437869060967677571ull;
ull pov1[N],pov2[N],inv1[N],inv2[N];
struct HASH{
ull val1,val2;
int len;
HASH(){val1=val2=0ull,len=0;}
HASH(int ip){val1=val2=ip,len=1;}
friend HASH operator +(const HASH &x,const HASH &y){
HASH z;
z.val1=x.val1*pov1[y.len]+y.val1;
z.val2=x.val2*pov2[y.len]+y.val2;
z.len=x.len+y.len;
return z;
}
friend HASH operator -(const HASH &x,const HASH &y){
HASH z;
z.val1=(x.val1-y.val1)*inv1[y.len];
z.val2=(x.val2-y.val2)*inv2[y.len];
z.len=x.len-y.len;
return z;
}
friend bool operator ==(const HASH &x,const HASH &y){
if(x.len!=y.len)return false;
if(x.val1!=y.val1)return false;
if(x.val2!=y.val2)return false;
return true;
}
}hs[N];
int n,anc[N][21],sa[N],rk[N],x[N],y[N],buc[N],m,ht[N],q,dep[N];
char s[N],t[N];
int HASH_LCP(int u,int v){
int len=0;
for(int i=20;i>=0;i--)if((dep[u]>=(1<<i))&&(dep[v]>=(1<<i))&&(hs[u]-hs[anc[u][i]])==(hs[v]-hs[anc[v][i]]))len+=(1<<i),u=anc[u][i],v=anc[v][i];
return len;
}
bool mat(int i,int j,int k){
if(y[i]!=y[j])return false;
if(!anc[i][k]&&!anc[j][k])return true;
if(anc[i][k]&&anc[j][k])return y[anc[i][k]]==y[anc[j][k]];
return false;
}
void SA(){
m=26;
for(int i=1;i<=n;i++)++buc[x[i]=s[i]];
for(int i=1;i<=m;i++)buc[i]+=buc[i-1];
for(int i=n;i;i--)sa[buc[x[i]]--]=i;
// for(int i=1;i<=n;i++)printf("%d ",sa[i]);puts("");
for(int k=0;k<=20;k++){
int num=0;
for(int i=1;i<=m;i++)buc[i]=0;
for(int i=1;i<=n;i++)if(!anc[i][k])y[++num]=i;else ++buc[x[anc[i][k]]];
for(int i=1;i<=m;i++)buc[i]+=buc[i-1];
for(int i=n;i;i--)if(anc[i][k])y[num+buc[x[anc[i][k]]]--]=i;
// printf("Y:");for(int i=1;i<=n;i++)printf("%d ",y[i]);puts("");
for(int i=1;i<=m;i++)buc[i]=0;
for(int i=1;i<=n;i++)++buc[x[y[i]]];
for(int i=1;i<=m;i++)buc[i]+=buc[i-1];
for(int i=n;i;i--)sa[buc[x[y[i]]]--]=y[i];
swap(x,y);
x[sa[1]]=num=1;
for(int i=2;i<=n;i++)x[sa[i]]=mat(sa[i],sa[i-1],k)?num:++num;
m=num;
// printf("S:");for(int i=1;i<=n;i++)printf("%d ",sa[i]);puts("");
// printf("X:");for(int i=1;i<=n;i++)printf("%d ",x[i]);puts("");
// puts("");
}
for(int i=1;i<=n;i++)rk[sa[i]]=i;
for(int i=1;i<n;i++)ht[i]=HASH_LCP(sa[i],sa[i+1]);
}
int L[N],R[N],pos[N],stk[N],tp,sum[N],lim;
int main(){
pov1[0]=pov2[0]=inv1[0]=inv2[0]=1;
for(int i=1;i<=NN;i++)pov1[i]=pov1[i-1]*sd1,pov2[i]=pov2[i-1]*sd2,inv1[i]=inv1[i-1]*iv1,inv2[i]=inv2[i-1]*iv2;
scanf("%d%d",&n,&q),lim=n;
for(int i=1,fa;i<=n;i++)scanf("%s%d",t,&anc[i][0]),s[i]=t[0]-'A'+1,hs[i]=HASH(s[i])+hs[anc[i][0]],dep[i]=dep[anc[i][0]]+1;
for(int i=1,len;i<=q;i++){
scanf("%s",t+1),len=strlen(t+1),reverse(t+1,t+len+1);
for(int j=1;j<=len;j++)s[n+j]=t[j]-'A'+1,dep[n+j]=j;
for(int j=2;j<=len;j++)anc[n+j][0]=n+j-1;
for(int j=1;j<=len;j++)hs[n+j]=HASH(s[n+j])+hs[anc[n+j][0]];
n+=len;
pos[i]=n;
}
// for(int i=1;i<=n;i++)printf("%d:%d ",i,dep[i]);puts("");
for(int j=1;j<=20;j++)for(int i=1;i<=n;i++)anc[i][j]=anc[anc[i][j-1]][j-1];
SA();
// for(int i=1;i<=n;i++)printf("%d:%2d ",i,sa[i]);puts("");
// for(int i=1;i<=n;i++)printf("%d:%2d ",i,ht[i]);puts("");
// for(int i=1;i<=n;i++)printf("%d:%2d ",i,dep[sa[i]]);puts("");
tp=0;
for(int i=1;i<=n;i++){
while(tp&&dep[sa[stk[tp]]]>ht[i])R[stk[tp]]=i,tp--;
if(dep[sa[i]]>ht[i])R[i]=i;
else stk[++tp]=i;
}
tp=0;
for(int i=n;i;i--){
while(tp&&dep[sa[stk[tp]]]>ht[i-1])L[stk[tp]]=i,tp--;
if(dep[sa[i]]>ht[i-1])L[i]=i;
else stk[++tp]=i;
}
// for(int i=1;i<=n;i++)printf("%d:(%d,%d)",i,L[i],R[i]);
for(int i=1;i<=n;i++)sum[i]=sum[i-1]+(sa[i]<=lim);
// for(int i=1;i<=n;i++)printf("%d %d %d\n",anc[i][0],i,s[i]);
for(int i=1;i<=q;i++){
pos[i]=rk[pos[i]];
printf("%d\n",sum[R[pos[i]]]-sum[L[pos[i]]-1]);
}
return 0;
}
还有一种方法则和蔼得多,就是我们这里讲解的AC自动机。
我们仍将名字串和翻转的询问串建出树来,不过这次是字典树。然后建出AC自动机。
考虑树。一个节点在树上的子树中,包含了所有有它作为后缀的串。所以我们只需要求出每个询问在树中的子树中有多少个来自原串的节点即可。
时间复杂度,其中是字符集大小。
代码:
#include<bits/stdc++.h>
using namespace std;
int n,m,cnt=1,res[1001000];
struct AC_Automaton{
int ch[26],fail,sz;
vector<int>v,ed;
}t[2001000];
char s[1001000];
void Ins(int id,int len){
int x=1;
for(int i=0;i<len;i++){
if(!t[x].ch[s[i]-'A'])t[x].ch[s[i]-'A']=++cnt;
x=t[x].ch[s[i]-'A'];
}
t[x].ed.push_back(id);
}
queue<int>q;
void build(){
for(int i=0;i<26;i++){
if(t[1].ch[i])t[t[1].ch[i]].fail=1,q.push(t[1].ch[i]),t[1].v.push_back(t[1].ch[i]);
else t[1].ch[i]=1;
}
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]),t[t[t[x].fail].ch[i]].v.push_back(t[x].ch[i]);
else t[x].ch[i]=t[t[x].fail].ch[i];
}
}
}
void dfs(int x){
for(auto y:t[x].v)dfs(y),t[x].sz+=t[y].sz;
for(auto i:t[x].ed)res[i]=t[x].sz;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=2,x;i<=n+1;i++)scanf("%s%d",s,&x),t[x+1].ch[s[0]-'A']=++cnt,t[cnt].sz=1;
for(int i=1,len;i<=m;i++)scanf("%s",s),len=strlen(s),reverse(s,s+len),Ins(i,len);
build(),dfs(1);
// for(int i=1;i<=cnt;i++)printf("%d<-%d %d\n",i,t[i].fail,t[i].sz);
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,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?