bzoj 2434: [Noi2011]阿狸的打字机
线段树,AC自动机应用
题意
初始字串为空,首先给定一系列操作序列,有三种操作:
- 在结尾加一个字符
- 在结尾删除一个字符
- 打印当前字串
然后多次询问第x个打印的字串在第y个打印的字串中出现了几次。
思路
操作字符串的过程实际是构建Trie的过程。新建字母相当于新加节点,打印字串相当于给标记,删除字母相当于回到爸爸节点。
因为Trie保存的是前缀,而子串相当于前缀的后缀,所以我们可以利用AC自动机的Fail指针:字符串u通过fail指针指向字符串v,表明v是u的子串。但是如果我们对于每个询问都直接处理,复杂度肯定过高。
又fail的性质是每个节点出度是1,所以反过来就是一棵树。u是v的爸爸意味着u代表的字串是v的子串。那么我们处理出fail树的dfs序,然后将询问离线按右端点排序,查询左端点的已经出现的fail树中的儿子个数即可。
维护一个线段树,对原Trie树进行遍历,每访问一个节点,就修改线段树,对线段树中该节点的DFS序起点的位置加上1。每往回走一步,就减去1。如果访问到了一个y字串的末尾节点,枚举询问中每个y串对应的x串,查询线段树中x串末尾节点从DFS序中的起始位置到结束位置的和,并记录答案。
然后。。。我他妈for(int i=0;i<strlen(s);i++)
成功把复杂度优化到了n方,然后gg了一晚上。
代码
#include <bits/stdc++.h>
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define M(a,b) memset(a,b,sizeof(a))
using namespace std;
const int MAXN=100007;
const int oo=0x3f3f3f3f;
char input[MAXN];
//AC自动机
struct AC
{
static const int maxnode=100007;
static const int sigma_size=26;
int ch[maxnode][sigma_size], val[maxnode], fail[maxnode], fa[maxnode];
int sz;//节点总数
void init()
{
sz=1;M(ch[0], 0);
}
inline int getidx(char c) { return c-'a'; }
void build()
{
int u=0, n=strlen(input);int tmp=0;
for(int i=0;i<n;i++)
{
if(input[i]=='P') val[++tmp]=u;
else if(input[i]=='B') u=fa[u];
else
{
int c=getidx(input[i]);
if(!ch[u][c])
{
M(ch[sz], 0);
ch[u][c]=sz;
fa[sz++]=u;
}
u=ch[u][c];
}
}
}
void getFail()
{
queue<int> q;
fail[0]=0;
for(int c=0;c<sigma_size;c++)
{
int u=ch[0][c];
if(u) { fail[u]=0;q.push(u); }
}
while(!q.empty())
{
int fr=q.front();q.pop();
for(int c=0;c<sigma_size;c++)
{
int u=ch[fr][c];
if(!u) continue;
q.push(u);
int v=fail[fr];
while(v&&!ch[v][c]) v=fail[v];
fail[u]=ch[v][c];
}
}
}
}ac;
//线段树
int stree[MAXN<<2];
void pushup(int rt) { stree[rt]=stree[rt<<1]+stree[rt<<1|1]; }
void build() { M(stree, 0); }
void update(int pos, int v, int l, int r, int rt)
{
if(l==r) { stree[rt]+=v;return; }
int mid=(l+r)>>1;
if(pos<=mid) update(pos, v, lson);
else update(pos, v, rson);
pushup(rt);
}
int query(int L, int R, int l, int r, int rt)
{
if(L<=l&&r<=R) return stree[rt];
int mid=(l+r)>>1;
int res=0;
if(L<=mid) res+=query(L, R, lson);
if(R>mid) res+=query(L, R, rson);
return res;
}
//fail树
int head[MAXN];
struct Edge
{
int to, ne;
Edge() {}
Edge(int a, int b) { to=a, ne=b; }
};
Edge e[MAXN];
int sume=0;
void addedge(int u, int v)
{
e[sume]=(Edge(v, head[u]));head[u]=sume;
sume++;
}
int in[MAXN], ou[MAXN], dfsnum=0;
void dfs(int u, int fa)
{
in[u]=++dfsnum;
for(int i=head[u];~i;i=e[i].ne)
{
int v=e[i].to;
if(v==fa) continue;
dfs(v, u);
}
ou[u]=dfsnum;
}
//离线询问
int hq[MAXN];
struct Query
{
int x, y;
int ne;
int res;
void rd(int _i)
{
scanf("%d%d", &x, &y);
res=0;ne=hq[y];
hq[y]=_i;
}
}q[MAXN];
void solve(int n)
{
int curpos=0;int tmp=1;
int len=strlen(input);
for(int i=0;i<len;i++)
{
if(input[i]=='P')
{
for(int k=hq[tmp];~k;k=q[k].ne)
{
int nodenum=ac.val[q[k].x];
q[k].res=query(in[nodenum], ou[nodenum], 1, n, 1);
}
tmp++;
}
else if(input[i]=='B')
{
update(in[curpos], -1, 1, n, 1);
curpos=ac.fa[curpos];
}
else
{
curpos=ac.ch[curpos][input[i]-'a'];
update(in[curpos], 1, 1, n, 1);
}
}
}
int main()
{
scanf("%s", input);
ac.init();sume=0;M(head, -1);build();M(hq, -1);
dfsnum=0;
ac.build();
ac.getFail();
for(int i=0;i<ac.sz;i++)
{
int u=i, v=ac.fail[i];
if(u!=v) addedge(v, u);
}
dfs(0, -1);
int n=dfsnum;
int m;scanf("%d", &m);
for(int i=1;i<=m;i++)
q[i].rd(i);
solve(n);
for(int i=1;i<=m;i++)
{
printf("%d\n", q[i].res);
}
//system("pause");
return 0;
}