NOI 2011 阿狸的打字机 AC自动机

Link
点我点我:-)

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

Descirption
打字机上只有28个按键,分别印有26个小写英文字母和’B’、’P’两个字母。经阿狸研究发现,这个打字机是这样工作的:

·输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最后)。
·按一下印有’B’的按键,打字机凹槽中最后一个字母会消失。
·按一下印有’P’的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失。

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

a aa ab 我们把纸上打印出来的字符串从1开始顺序编号,一直到n。打字机有一个非常有趣的功能,在打字机中暗藏一个带数字的小键盘,在小键盘上输入两个数(x,y)(其中1≤x,y≤n),打字机会显示第x个打印的字符串在第y个打印的字符串中出现了多少次。
阿狸发现了这个功能以后很兴奋,他想写个程序完成同样的功能,你能帮助他么?

Input & Output
input:

输入的第一行包含一个字符串,按阿狸的输入顺序给出所有阿狸输入的字符。
第二行包含一个整数m,表示询问个数。
接下来m行描述所有由小键盘输入的询问。其中第i行包含两个整数x, y,表示第i个询问为(x, y)。

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

Sample
input1:
aPaPBbP
3
1 2
1 3
2 3
output1:
2
1
0

Hint
对于100%的数据,n<=100000,m<=100000, 第一行总长度<=100000。

Analysis
AC自动机好题!
首先建一棵AC自动机,然后按照它的fail边建一棵树(根据AC自动机的性质可知fail边组成的一定是棵树)
再对这棵树做一个Dfs遍历出来一个括号序列
我们定义一个串的编号为它在AC自动机中Tire的结尾节点的编号
考虑字符串x在字符串y中出现的次数,发现其为在fail树中x的子树中y到x经过的点数(含x, y)

那么可以在括号序列上维护一个树状数组,
每次加字符就是跳到新节点然后在它的左括号上加1,
删字符就是在左括号上减1然后跳回爸爸
询问就是处理以它为y的所有询问,枚举x,求在x的子树中(即x的左右括号间)和,就是x的子树中y到x经过的点数(含x, y)

Code

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>

using namespace std;

#define pb push_back
#define For(i, a, b) for(int i = (a); i <= (int)(b); ++i)
#define N (200000+5)

int n, bn, dfn, fir[N], sec[N], ans[N], bh[N], fbh[N];
char r[N];
vector<int> G[N], q[N], id[N];

struct BIT{
    int c[N];
    int lowerbit(int x){return x&(-x);}

    void Add(int now, int v){
        while(now < N){
            c[now] += v; now += lowerbit(now);
        }
    }

    int Sum(int now){
        int ret = 0;
        while(now > 0){
            ret += c[now]; now -= lowerbit(now);
        }
        return ret;
    }
}bit;

struct AC_Automaton{
    int Cnt, ch[N][30], fa[N], fail[N];

    void Insert(){
        int now = 0;
        char nc;

        For(nr, 1, n){
            nc = r[nr];
            if(nc == 'B'){now = fa[now]; continue;}
            if(nc == 'P'){bh[now] = ++bn; fbh[bn] = now; continue;}

            nc -= 'a';
            if(!ch[now][nc]){ch[now][nc] = ++Cnt; fa[Cnt] = now;}
            now = ch[now][nc];
        }
    }

    void Getfail(){
        queue<int> q;
        int now, nxt, fs;

        For(i, 0, 25){
            now = ch[0][i];
            if(now) G[0].pb(now), q.push(now), fail[now] = 0;
        }

        while(!q.empty()){
            now = q.front(); q.pop();
            For(i, 0, 25){
                nxt = ch[now][i]; fs = ch[fail[now]][i];
                if(!nxt) ch[now][i] = fs;
                else{
                    fail[nxt] = fs;
                    G[fs].pb(nxt); q.push(nxt);
                }
            }
        }
    }

    void Dfs(int now){
        fir[now] = ++dfn;
        For(i, 0, G[now].size()-1) Dfs(G[now][i]);
        sec[now] = ++dfn;
    }

    void Solve(){
        int o = 0, now, myid, myx;
        char nc;

        For(nr, 1, n){
            nc = r[nr]; 
            if(nc == 'B'){bit.Add(fir[o], -1); o = fa[o]; continue;}
            if(nc != 'P'){o = ch[o][nc-'a']; bit.Add(fir[o], 1); continue;}

            now = bh[o];
            For(i, 0, q[now].size()-1){
                myx = fbh[q[now][i]], myid = id[now][i];
                ans[myid] = bit.Sum(sec[myx]) - bit.Sum(fir[myx]-1);
            }
        }
    }
}ac;

int main(){
#ifndef ONLINE_JUDGE
    freopen("test.in", "r", stdin);
    freopen("test.out", "w", stdout);
#endif

    int m, x, y;
    scanf("%s%d", r+1, &m);
    n = strlen(r+1);

    ac.Insert(); ac.Getfail(); ac.Dfs(0); 
    For(i, 1, m){
        scanf("%d%d", &x, &y);
        q[y].pb(x); id[y].pb(i);
    }

    ac.Solve();
    For(i, 1, m) printf("%d\n", ans[i]);

    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据引用[1]和引用的描述,这是一道关于图论的问题,需要设计一个软件来计算给定的建造方案所需要的费用,或者计算在W星球上修建n-1条双向道路使得国家之间连通的方案。具体来说,对于引用,需要计算每条道路的修建费用,而对于引用,需要构建一个连通的图,使得图中任意两个节点之间都有一条路径。下面是两个问题的解答: 1. 对于引用,我们可以使用图论中的最小生成树算法来解决。最小生成树算法可以保证在连接所有节点的情况下,总的修建费用最小。常见的最小生成树算法有Prim算法和Kruskal算法。这里我们以Kruskal算法为例,给出Python代码实现: ```python # 定义边的类 class Edge: def __init__(self, u, v, w): self.u = u self.v = v self.w = w # 定义并查集类 class UnionFind: def __init__(self, n): self.parent = list(range(n)) self.rank = [0] * n def find(self, x): if self.parent[x] != x: self.parent[x] = self.find(self.parent[x]) return self.parent[x] def union(self, x, y): px, py = self.find(x), self.find(y) if px == py: return False if self.rank[px] < self.rank[py]: self.parent[px] = py elif self.rank[px] > self.rank[py]: self.parent[py] = px else: self.parent[py] = px self.rank[px] += 1 return True # Kruskal算法 def kruskal(n, edges): uf = UnionFind(n) edges.sort(key=lambda x: x.w) res = 0 for e in edges: if uf.union(e.u, e.v): res += e.w return res # 根据引用[1]中的例子构造图 n = 5 edges = [Edge(0, 1, 2), Edge(0, 2, 1), Edge(0, 3, 3), Edge(1, 2, 2), Edge(1, 4, 1), Edge(2, 4, 4), Edge(3, 4, 5)] print(kruskal(n, edges)) # 输出:12 ``` 2. 对于引用,我们可以使用随机化算法来构造一个连通的图。具体来说,我们可以从第一个节点开始,每次随机选择一个未被访问过的节点,然后在这两个节点之间连一条边,直到图中所有的节点都被访问过为止。这样构造出来的图一定是连通的,并且边的数量为n-1。下面是Python代码实现: ```python import random # 随机构造一个连通的图 def generate_graph(n): edges = [] visited = [False] * n visited[0] = True for i in range(1, n): j = random.randint(0, i - 1) edges.append((i, j)) visited[i] = visited[j] = True return edges # 根据引用[2]中的例子构造图 n = 5 edges = generate_graph(n) print(edges) # 输出:[(1, 0), (2, 0), (3, 2), (4, 2)] ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值