noi 2011 阿狸的打字机

【问题描述】

阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。打字机

上只有 28 个按键,分别印有 26 个小写英文字母和'B''P'两个字母。

经阿狸研究发现,这个打字机是这样工作的:

输入小写字母,打字机的一个凹槽中会加入这个字母( P 前凹槽中至

少有一个字母)

按一下印有'B'的按键,打字机凹槽中最后一个字母会消失。

按一下印有'P'的按键,打字机会在纸上打印出凹槽中现有的所有字母并

换行,但凹槽中的字母不会消失(保证凹槽中至少有一个字母)。

例如,阿狸输入 aPaPBbP,纸上被打印的字符如下:

a

aa

ab

我们把纸上打印出来的字符串从 1 开始顺序编号,一直到 n。打字机有一个

非常有趣的功能,在打字机中暗藏一个带数字的小键盘,在小键盘上输入两个数

(x,y)(其中 1x,yn),打字机会显示第 x 个打印的字符串在第 y 个打印的字符串

中出现了多少次。

阿狸发现了这个功能以后很兴奋,他想写个程序完成同样的功能,你能帮助

他么?

【输入格式】

从文件 type.in 中读入数据。

输入的第一行包含一个字符串,按阿狸的输入顺序给出所有阿狸输入的字符。

第二行包含一个整数 m,表示询问个数。

接下来 m 行描述所有由小键盘输入的询问。其中第 i 行包含两个整数 x, y

表示第 i 个询问为(x, y)

【输出格式】

输出到文件 type.out 中。

输出 m 行,其中第 i 行包含一个整数,表示第 i 个询问的答案。

【样例输入】

aPaPBbP

3

1 2

1 3

2 3

【样例输出】

2

1

0

noi <wbr>2011 <wbr>阿狸的打字机

 

 

用失败指针反向建树的原因网上很多了,这里讲下后面处理的细节。

建完树只是找到了一个新思路,但是还是无法解决如何在以x串最后一个字符所在的节点为根的子树中找到包含多少个y串中的节点。如果你已经把每个节点所在的串标记了,怎么寻找呢?

再考虑,一棵树中以某节点为根的子树能满足那些性质呢?dfs序是连续的。所以我们可以先求出树的dfs序,确定以每个节点为根的子树所对应的区间。

然后呢?如果询问的是(x,y),那么我们就想办法x串最后一个字符所在节点为根的子树中的y串上的节点独立出来。一个想法就是把它们赋成1,其他的点赋成0,然后就是树状数组或者线段树的区间求和操作了。

但是我们不可能对每个询问都清空树上面的标志在打上新的标志。所以就想:对于每次标记完的树都要尽可能地利用这些标记。显然。对于所有询问我们就可以把每一个y标记完的树,处理所有对应的(x[i],y)询问。

由于输入数据的特殊性。。。只要跟着输入的串在树上一步一步地走就可以了:

1.碰到B取消标志往上走。(trie树上。标志是映射到dfs序上的标志)

2.碰到P处理询问。

3.否则继续往下走并打上标记。(同1

 

AC CODE

 

program noi_2011_day1_type;
var g1,g2,g3,next,last,en,tail:array[1..100000] of longint;
    e,q,fa,ps,pt,fail,ans:array[1..100000] of longint;
    trie:array[1..100000,1..26] of longint;
    c:array[1..200000] of longint;
    s:array[1..100000] of char;
    tot,size,len,len1,len2,l,r,n:longint;
//============================================================================
procedure ins(x,y:longint);
begin
  inc(len1); g1[len1]:=y;
  next[len1]:=en[x]; en[x]:=len1;
end;
//============================================================================
procedure insert(x,y,z:longint);    //本来打算对询问排序一下一起搞的。。看到网上建图的奇葩做法顿觉我弱爆了。。。
begin
  inc(len2); g2[len2]:=y; g3[len2]:=z;
  last[len2]:=tail[x]; tail[x]:=len2;
end;
//============================================================================
procedure add(x,y:longint);    //树状数组的修改操作。(添加和取消标号一起搞,加上±1)
begin
  while x<=size do
  begin
    inc(c[x],y);
    inc(x,x and -x);
  end;
end;
//============================================================================
function sum(x:longint):longint;    //树状数组的求和操作。
begin sum:=0;
  while x>0 do
  begin
    inc(sum,c[x]);
    dec(x,x and -x);
  end;
end;
//============================================================================
procedure init;
var now,tmp:longint;
    ch:char;
begin
  read(ch); tot:=1; now:=1; n:=0;
  while ch in ['B','P','a'..'z'] do
  begin
    inc(len); s[len]:=ch;
    if ch='B' then now:=fa[now] else
    if ch='P' then
    begin
      inc(n); e[n]:=now;
    end else
    begin
      tmp:=ord(ch)-ord('a')+1;
      if trie[now,tmp]<>0 then now:=trie[now,tmp] else    //建立trie树。
      begin
        inc(tot); trie[now,tmp]:=tot;
        fa[tot]:=now; now:=tot;
      end;
    end; read(ch);
  end;
end;
//============================================================================
procedure get_fail;    //生成失败指针。顺便反向建树。
var g,h,i,j,now:longint;
    tmp:char;
begin
  l:=1; r:=0; fail[1]:=0;
  for i:=1 to 26 do    //后来写的一个AC自动机引入一个-1点貌似在求失败指针的时候更简洁。
  begin
    if trie[1,i]<>0 then
    begin
      inc(r); q[r]:=trie[1,i];
      fail[q[r]]:=1; ins(1,q[r]);  //建树。
    end;
  end;
  while l<=r do
  begin g:=r;
    for h:=l to g do
    begin now:=q[h];
      for i:=1 to 26 do
      begin
        if trie[now,i]<>0 then
        begin
          inc(r); q[r]:=trie[now,i]; j:=fail[now];
          while j<>0 do
            if trie[j,i]<>0 then
            begin
              fail[q[r]]:=trie[j,i];
              ins(trie[j,i],q[r]); break;    //建树。
  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章