【NOI2011】阿狸的打字机

Description

阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。打字机上只有28个按键,分别印有 26个小写英文字母和’B’、’P’两个字母。 经阿狸研究发现,这个打字机是这样工作的:

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

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

按一下印有'P'的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失(保证凹槽中至少有一个字母) 。 

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

a

aa

ab

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

1≤n≤ 10^5
1≤m≤ 10^5
输入文件第一行的字符数≤10^5

Analysis

ganxi大佬说要补专题,那就写一道AC自动机练手吧

首先,trie肯定要建出来
我们要求点x代表的串(用x简写)在y中出现了多少次
一个暴力的想法是,从root到y每个点遍历,顺着fail链往上跳,如果跳到x就累计一次
因为,fail[x]一定是x的后缀
但是时间不允许
尝试把fail树建出来
fail树中,x在所有x的子树里的点出现了一次
那么为了统计答案,只需求出某点子树内累计的点数
弄出dfs序用树状数组来搞即可

Code

#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,b,a) for(int i=b;i>=a;i--)
#define efo(i,v) for(int i=last[v];i;i=next[i])
using namespace std;
typedef long long ll;
const int N=1e5+5;
int n,m,tot,now,pre[N],to[N*2],next[N*2],last[N];
int a[N],le[N],ri[N],c[N*2],ans[N];
char s[N];
queue<int> q;
struct node
{
    int x,y,id;
}b[N];
struct trie
{
    int fail,son[30];
}tr[N];
void read(int &t)
{
    t=0;char ch;
    for(ch=getchar();ch<'0' || ch>'9';ch=getchar());
    for(;'0'<=ch && ch<='9';ch=getchar()) t=t*10+ch-'0';
}
bool cmp(node a,node b){return a.y<b.y;}
void link(int u,int v)
{
    to[++tot]=v,next[tot]=last[u],last[u]=tot;
}
void getfail()
{
    q.push(1);
    while(!q.empty())
    {
        int u=q.front();q.pop();
        fo(i,0,25)
        {
            int v=tr[u].son[i];
            if(!v) continue;
            int x=tr[u].fail;
            for(;x && !tr[x].son[i];x=tr[x].fail);
            if(tr[x].son[i]) tr[v].fail=tr[x].son[i];
            else tr[v].fail=1;
            q.push(v);
        }
    }
}
void dfs(int v)
{
    le[v]=++now;
    efo(i,v) dfs(to[i]);
    ri[v]=now;
}
int lowbit(int x){return x&-x;}
void add(int x,int y)
{
    for(int i=x;i<=now;i+=lowbit(i)) c[i]+=y;
}
int query(int x)
{
    int t=0;
    for(int i=x;i;i-=lowbit(i)) t=t+c[i];
    return t;
}
int main()
{
    scanf("%s\n",s+1);
    n=strlen(s+1);
    int Q;
    read(Q);
    fo(i,1,Q) b[i].id=i,read(b[i].x),read(b[i].y);
    sort(b+1,b+Q+1,cmp);
    m=1;
    int v=1,t=0;
    fo(i,1,n)
        if(s[i]=='P') a[++t]=v;
        else
        if(s[i]=='B') v=pre[v];
        else tr[v].son[s[i]-'a']=++m,pre[m]=v,v=m;
    getfail();
    fo(i,2,m) link(tr[i].fail,i);
    dfs(1);
    int j=1;t=0;v=1;
    fo(i,1,n)
        if(s[i]=='P')
        {
            t++;
            for(;b[j].y==t;j++)
            {
                int x=a[b[j].x];
                ans[b[j].id]=query(ri[x])-query(le[x]-1);
            }
        }
        else
        if(s[i]=='B')
        {
            add(le[v],-1);
            v=pre[v];
        }
        else
        if('a'<=s[i] && s[i]<='z')
        {
            v=tr[v].son[s[i]-'a'];
            add(le[v],1);
        }
    fo(i,1,Q) printf("%d\n",ans[i]);
    return 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、付费专栏及课程。

余额充值