【Codeforces】 CF587F Duff is Mad

题目链接

CF方向
Luogu方向

题目解法

很好的一道题,让我对根号分治及 A C AC AC 自动机有了更深的理解
看到字符串出现次数的题,首先想到建立 A C AC AC 自动机
如何找到字符串 x x x 在字符串 y y y 中的出现次数?字符串 y y y t r i e trie trie 树上一直往上跳,然后再 f a i l fail fail 树上看当前 y y y 的前缀对应点是否在 x x x 对应点的子树内,然后累加

考虑把 [ l , r ] [l,r] [l,r] 的字符串变为 [ 1 , r ] [1,r] [1,r] 的字符串 −    [ 1 , l − 1 ] -\;[1,l-1] [1,l1] 的字符串
我们需要计算一个前缀中的字符串在 S k S_k Sk 中的出现次数
现在可以得到 2 种暴力

  1. S k S_k Sk 的前缀(不是上面的前缀,而是字符串的前缀)在 f a i l fail fail 树上标记出,然后做一个子树累加
    那么每个字符串在 S k S_k Sk 中的出现次数即为 f a i l fail fail 树上末位置对应的子树和
  2. 从前往后遍历每个字符串,把 f a i l fail fail树上这个字符串对应的子树 + 1 +1 +1
    然后对于 S k S_k Sk,遍历 S k S_k Sk 的前缀,求 f a i l fail fail 树的单点值累加即可

考虑糅合两个做法:根号分治
对于 ≥ B \ge B B 的字符串,用做法 1
对于 < B <B 的字符串,用做法 2
两个做法都需要离线
理论上 B B B n \sqrt n n 最优,时间复杂度为 O ( n n ) O(n\sqrt n) O(nn )(这里不分 n , q n,q n,q 和字符串的总长度)
但是空间有些开不下,所以可以调小块长

#include 
using namespace std;
typedef long long LL;
const int N=100100,MAXB=295;
struct Node{ int k,neg,id;};
int n,q,lenth[N];
string str[N];
int tr[N][26],idx,ne[N],ed[N];
int bigidx,rv[N];
int L[N],R[N],dfnidx=-1;
int e[N],NE[N],h[N],IDX;
vector<Node> query[N];
int que[N],sum[N];
LL ans[N],pref[MAXB][N];
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
void add(int x,int y){ e[IDX]=y,NE[IDX]=h[x],h[x]=IDX++;}
int insert(string s){
	int p=0;
	for(int i=0;i<s.size();i++){
		int c=s[i]-'a';
		if(!tr[p][c]) tr[p][c]=++idx;
		p=tr[p][c];
	}
	return p;
}
void build_ACAM(){
	int hh=0,tt=-1;
	for(int i=0;i<26;i++) if(tr[0][i]) que[++tt]=tr[0][i];
	while(hh<=tt){
		int u=que[hh++];
		for(int i=0;i<26;i++){
			int v=tr[u][i];
			if(!v) tr[u][i]=tr[ne[u]][i];
			else ne[v]=tr[ne[u]][i],que[++tt]=v;
		}
	}
}
void dfs(int u){
	for(int i=h[u];~i;i=NE[i]) dfs(e[i]),sum[u]+=sum[e[i]];
}
void dfs_dfn(int u){
	L[u]=++dfnidx;
	for(int i=h[u];~i;i=NE[i]) dfs_dfn(e[i]);
	R[u]=dfnidx;
}
struct BLOCK{
	int LIM,pos[N],tag1[N],tag2[N];
	void build(){
		LIM=sqrt(idx);
		for(int i=1;i<=idx;i++) pos[i]=(i-1)/LIM+1;
	}
	void add(int l,int r){
		if(pos[l]==pos[r]) for(int i=l;i<=r;i++) tag1[i]++;
		else{
			int i=l,j=r;
			while(pos[i]==pos[l]) tag1[i]++,i++;
			while(pos[j]==pos[r]) tag1[j]++,j--;
			for(int k=pos[i];k<=pos[j];k++) tag2[k]++;
		}
	}
	int ask(int x){ return tag1[x]+tag2[pos[x]];}
}block;
int main(){
	cin>>n>>q;
	for(int i=1;i<=n;i++) cin>>str[i],lenth[i]=str[i].size();
	int m=0;
	for(int i=1;i<=n;i++) m+=lenth[i];
	for(int i=1;i<=n;i++) ed[i]=insert(str[i]);
	build_ACAM();
	memset(h,-1,sizeof(h));
	for(int i=1;i<=idx;i++) add(ne[i],i);
	int B=290;
	for(int i=1;i<=n;i++)
		if(lenth[i]>B){
			rv[i]=++bigidx;
			for(int j=0;j<=idx;j++) sum[j]=0;
            int p=0;
            for(int j=0;j<str[i].size();j++){
                p=tr[p][str[i][j]-'a'];
                sum[p]++;
            }
			dfs(0);
			LL res=0;
			for(int j=1;j<=n;j++) res+=sum[ed[j]],pref[bigidx][j]=res;
		}
	for(int i=1;i<=q;i++){
		int l=read(),r=read(),k=read();
		if(lenth[k]>B) k=rv[k],ans[i]=pref[k][r]-pref[k][l-1];
		else query[r].push_back({k,1,i}),query[l-1].push_back({k,-1,i});
	}
	dfs_dfn(0);
	block.build();
	for(int i=1;i<=n;i++){
        block.add(L[ed[i]],R[ed[i]]);
		for(auto j:query[i]){
            int x=j.k,p=0;
            for(int k=0;k<str[x].size();k++){
                p=tr[p][str[x][k]-'a'];
                ans[j.id]+=j.neg*block.ask(L[p]);
            }
		}
	}
	for(int i=1;i<=q;i++) cout<<ans[i]<<'\n';
	return 0;
}

你可能感兴趣的:(Codeforces,算法)