[ICPC2019 WF]First of Her Name

X.[ICPC2019 WF]First of Her Name

这题的一种解法是把所有东西(名字串和翻转的询问串)建出一棵树来,然后跑树上后缀排序,用二分+哈希求出\(height\)数组,然后使用单调栈找出每个位置最多能够向左向右延伸多远。但是按照某人的说法出题人好像出了卡此算法的数据,所以最后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自动机。

考虑\(fail\)树。一个节点在\(fail\)树上的子树中,包含了所有有它作为后缀的串。所以我们只需要求出每个询问在\(fail\)树中的子树中有多少个来自原串的节点即可。

时间复杂度\(\sum|S|\times|\alpha|\),其中\(|\alpha|\)是字符集大小。

代码:

#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;
}
posted @ 2021-03-30 14:38  Troverld  阅读(126)  评论(0编辑  收藏  举报