BZOJ2434【NOI2011】阿狸的打字机题解(AC自动机+dfs序+树状数组)

题目:BZOJ2434.
题目大意:有三个操作:
1.加入一个字符.
2.删除一个字符.
3.将当前字符串记录下来.
设将所有记录下来的字符串标上标号 1 1 1~ n n n,给出一些询问,每次询问 ( x , y ) (x,y) (x,y)表示询问第 x x x个字符串在第 y y y个字符串中出现的次数.
1 ≤ n ≤ 1 0 5 1\leq n\leq 10^5 1n105.

这道题即使告诉了我是dfs序我也没想到离线…

首先这道题可以离线,所以考虑离线建出AC自动机.然后考虑遍历AC自动机,遍历到一个点给这个点加1,退出一个点就给这个点减1,当遍历到一个点是第y个串的结尾时,就回答所有询问(x,y).

考虑询问(x,y)如何求解,我们回忆fail树的意义,fail树上每一个节点的子树中的节点都是以这个节点为后缀的.再考虑我们要的询问,其实就是询问一个表示y串的节点到根之间有多少个节点可以通过跳fail指针跳到x.

所以我们处理询问(x,y)只需要处理在fail树上x的子树有多少个y串上的节点,这个用dfs序+树状数组就可以处理.

代码如下:

#include<bits/stdc++.h>
  using namespace std;

#define Abigail inline void
typedef long long LL;
#define m(a) memset(a,0,sizeof(a))

const int N=100000,C=26;

struct Trie{
  int s[C],fail,fa;
}tr[N+9];
int cn,d[N+9];
vector<int> y[N+9];

void Build(char *c,int len){
  tr[cn=0]=Trie();
  int x=0,n=0;
  for (int i=1;i<=len;++i)
    switch (c[i]){
      case 'B':
      	x=tr[x].fa;
      	break;
      case 'P':
      	y[x].push_back(++n);
      	d[n]=x;
      	break;
      default:
      	if (tr[x].s[c[i]-'a']) x=tr[x].s[c[i]-'a'];
      	else {
      	  tr[x].s[c[i]-'a']=++cn;
      	  tr[cn]=Trie();
      	  tr[cn].fa=x;
      	  x=cn;
      	}
    }
}

struct side{
  int y,next;
}e[N*2+9];
int lin[N+9],top;

void ins(int x,int y){
  e[++top].y=y;
  e[top].next=lin[x];
  lin[x]=top;
}

queue<int> que;

void Get_fail(){
  for (int i=0;i<C;++i)
    if (tr[0].s[i]) que.push(tr[0].s[i]);
  while (!que.empty()){
    int x=que.front(),t;que.pop();
    for (int i=0;i<C;++i)
      if (tr[x].s[i]){
      	t=tr[x].fail;
      	while (t&&!tr[t].s[i]) t=tr[t].fail;
      	tr[tr[x].s[i]].fail=tr[t].s[i];
		que.push(tr[x].s[i]); 
      }
  }
  for (int i=1;i<=cn;++i)
    ins(tr[i].fail,i);
}

int dfn[N+9],cd,low[N+9];

void dfs_dfn(int x){
  dfn[x]=++cd;
  for (int i=lin[x];i;i=e[i].next)
    dfs_dfn(e[i].y);
  low[x]=cd;
}

int c[N+9];

void Add(int x,int v){for (;x<=cd;x+=x&-x) c[x]+=v;}
int Query(int x){int sum=0;for (;x;x-=x&-x) sum+=c[x];return sum;}

vector<int>q[N+9],id[N+9];
int ans[N+9];

void dfs_ans(int x){
  Add(dfn[x],1);
  int siz=y[x].size(),ss,t;
  for (int i=0;i<siz;++i){
  	t=y[x][i];ss=q[t].size();
  	for (int j=0;j<ss;++j)
	  ans[id[t][j]]=Query(low[d[q[t][j]]])-Query(dfn[d[q[t][j]]]-1);
      //一定要处理好串与节点间的关系啊
  }
  for (int i=0;i<C;++i)
    if (tr[x].s[i]) dfs_ans(tr[x].s[i]);
  Add(dfn[x],-1);
}

char s[N+9];
int n;

Abigail into(){
  scanf("%s",s+1);
  n=strlen(s+1);
  Build(s,n);
  scanf("%d",&n);
  int x,y;
  for (int i=1;i<=n;++i){
  	scanf("%d%d",&x,&y);
  	q[y].push_back(x);
  	id[y].push_back(i);
  }
}

Abigail work(){
  Get_fail();
  dfs_dfn(0);
  dfs_ans(0);
}

Abigail outo(){
  for (int i=1;i<=n;++i)
    printf("%d\n",ans[i]);
}

int main(){
  into();
  work();
  outo();
  return 0; 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值