zoukankan      html  css  js  c++  java
  • 【CF587F】Duff is Mad(AC自动机+根号分治)

    点此看题面

    • 给定(n)个字符串,(q)次询问,每次求(s_{lsim r})(s_k)中的出现次数总和。
    • (n,q,sum|s|le10^5)

    (AC)自动机+根号分治

    由于询问的若干串之间不相干,显然可以把询问拆成用(s_{1sim r})的答案减去(s_{1,l-1})的答案,然后就变成了每次询问前(i)个串在(s_k)中的出现次数。

    而众所周知,在(AC)自动机上,子串是前缀的后缀,前缀就是根到(s_k)路径上的每个节点,后缀就是一个节点的所有祖先。反过来也可以看作是根到(s_k)路径上的点在询问串对应的子树内。

    考虑(|s_k|),把情况分成小于等于(sqrt N)和大于(sqrt N)两类。

    如果(|s_k|lesqrt N),我们直接枚举把询问的(s_k)扔到(l-1)(r)两个端点上,然后只要枚举每个串在(fail)树上给子树打标记(转化成(dfs)序列后用树状数组),然后处理对应端点上的询问求到(s_k)路径上的所有点的标记和即可。

    如果(|s_k|>sqrt N),由于这样的串不超过(sqrt N)个,我们把询问扔给(s_k),对于每个这样的(s_k)分别求解答案。具体地,我们只要给到(s_k)路径上的所有点打上标记,枚举每个串求出子树内的标记和,然后前缀和+差分即可。

    注意到这里的树状数组可以用分块替代消去(log),但实际上没啥意义,估计树状数组小常数跑得更快。

    代码:(O(nsqrt nlogn))

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 100000
    #define LL long long
    using namespace std;
    int n,Nt=1,sz,id[N+5],bg[N+5],l[N+5];LL v[N+5],ans[N+5];char s[N+5];
    struct Q {int p,x,y;I Q(CI i=0,CI a=0,CI b=0):p(i),x(a),y(b){}};vector<Q> V[N+5],G[N+5];vector<Q>::iterator it;
    namespace AC//AC自动机
    {
    	#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
    	struct node {int F,S[30];}O[N+5];
    	int d,dI[N+5],dO[N+5],ee,lnk[N+5];struct edge {int to,nxt;}e[N+5];
    	I void Init(CI x=1) {dI[x]=++d;for(RI i=lnk[x];i;i=e[i].nxt) Init(e[i].to);dO[x]=d;}//处理dfs序
    	struct TreeArray
    	{
    		int a[N+5];I void U(RI x,CI v) {W(x<=Nt) a[x]+=v,x+=x&-x;}//单点修改/后缀修改
    		I int Q(RI x,RI t=0) {W(x) t+=a[x],x-=x&-x;return t;}//前缀询问/单点询问
    	}A;
    	I int Ins(char* s,CI l)//插入一个串
    	{
    		RI x=1;for(RI i=1,t;i<=l;++i) !O[x].S[t=s[i]&31]&&(O[x].S[t]=++Nt),x=O[x].S[t];return x;//返回节点编号
    	}
    	int q[N+5];I void Build()//建AC自动机
    	{
    		RI i,k,H=1,T=0;for(i=1;i<=26;++i) (O[1].S[i]?O[q[++T]=O[1].S[i]].F:O[1].S[i])=1;
    		W(H<=T) for(k=q[H++],i=1;i<=26;++i) (O[k].S[i]?O[q[++T]=O[k].S[i]].F:O[k].S[i])=O[O[k].F].S[i];
    		for(i=2;i<=Nt;++i) add(O[i].F,i);Init();//建fail树
    	}
    	I void P(char* s,CI l,CI v) {for(RI i=1,x=1;i<=l;++i) x=O[x].S[s[i]&31],A.U(dI[x],v);}//给路径上所有点打标记
    	I int G(CI x) {return A.Q(dO[x])-A.Q(dI[x]-1);}//询问子树标记和
    	I void U(CI x) {A.U(dI[x],1),A.U(dO[x]+1,-1);}//给子树打标记
    	I LL Q(char* s,CI l) {LL t=0;for(RI i=1,x=1;i<=l;++i) x=O[x].S[s[i]&31],t+=A.Q(dI[x]);return t;}//求路径上所有点标记和
    }
    int main()
    {
    	RI Qt,i,j;for(scanf("%d%d",&n,&Qt),i=1;i<=n;++i)
    		bg[i]=bg[i-1]+l[i-1],scanf("%s",s+bg[i]+1),id[i]=AC::Ins(s+bg[i],l[i]=strlen(s+bg[i]+1));
    	RI x,y,k;for(sz=sqrt(n),i=1;i<=Qt;++i) scanf("%d%d%d",&x,&y,&k),
    		l[k]<=sz?(V[x-1].push_back(Q(i,k,-1)),V[y].push_back(Q(i,k,1))):G[k].push_back(Q(i,x,y));//根号分治
    	for(AC::Build(),i=1;i<=n;++i) if(!G[i].empty())//对于长度超过sqrt(n)的串
    	{
    		for(AC::P(s+bg[i],l[i],1),j=1;j<=n;++j) v[j]=v[j-1]+AC::G(id[j]);//前缀和
    		for(it=G[i].begin();it!=G[i].end();++it) ans[it->p]=v[it->y]-v[it->x-1];AC::P(s+bg[i],l[i],-1);//差分回应询问
    	}
    	for(i=1;i<=n;++i)//对于长度不超过sqrt(n)的串
    	{
    		for(AC::U(id[i]),it=V[i].begin();it!=V[i].end();++it) ans[it->p]+=it->y*AC::Q(s+bg[it->x],l[it->x]);//加入每个前缀,枚举端点上的询问
    	}
    	for(i=1;i<=Qt;++i) printf("%lld
    ",ans[i]);return 0;
    }
    
    败得义无反顾,弱得一无是处
  • 相关阅读:
    如何在现有SuperMemo UX课程中快速新增学习材料?
    EmEditor的妙用:快速预览html网页
    [编程心得]PyQt中“明天”的表示法
    iTunes U:数字1到19的发音视频
    写个 LINQ的分组
    VS 添加链接文件 好容易忘记, 备注一下。
    发个代码
    批量修改 表结构
    你的5个需求层次 认识自身3
    动态规划,必需这样,必需不能这样,是快速解决某类问题的关键所在。 认识自身7
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/CF587F.html
Copyright © 2011-2022 走看看