【NOI2011】阿狸的打字机

原题

阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。打字机上只有28个按键,分别印有 26个小写英文字母和’B’、’P’两个字母。 经阿狸研究发现,这个打字机是这样工作的:
输入小写字母,打字机的一个凹槽中会加入这个字母(按 P 前凹槽中至少有一个字母)。
按一下印有’B’的按键,打字机凹槽中最后一个字母会消失。
按一下印有’P’的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失(保证凹槽中至少有一个字母) 。
例如,阿狸输入 aPaPBbP,纸上被打印的字符如下:
a
aa
ab
我们把纸上打印出来的字符串从 1开始顺序编号,一直到 n。打字机有一个非常有趣的功能,在打字机中暗藏一个带数字的小键盘,在小键盘上输入两个数(x,y)(其中1≤x,y≤n),打字机会显示第x个打印的字符串在第y个打印的字符串中出现了多少次。 阿狸发现了这个功能以后很兴奋,他想写个程序完成同样的功能,你能帮助他么?

题解

比较几个匹配串,一般来说AC自动机是一个很好的选择。
根据输入的第一个长字符串,考虑P和B表示什么。
建造Trie的时候,P表示标记,B表示回到自己的父亲,小写字母表示向下走。
但是有这么多组询问,怎么用很短的时间解决掉他们呢?
题目条件:
①被打印的字符串是有序的,即Trie上的一个节点表示被打印的字符串的一个区间[l,r]。
②问一个串在另一个串中出现了几次。
相应对策:
那么再跑一遍Trie,就可以解决掉[l,r]区间的问题。
只需要在进来这个点时在数据结构中+1,出去时-1即可。
问次数,就是问在fail树上,根节点到某个其子树种节点的路径上,有几个+1。

代码

#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
#define N 100010
#define P(a) putchar(a)
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
struct note{
    int id,u,v;
};note qu[N];
struct note1{
    int to,next;
};note1 edge[N];
int tot,head[N],ans[N];
int i,j,k,l,n,m,x,gs,cs,wz,y;
int c[N],u,v;
int tr[N][26],fail[N],bz[N],pre[N];
int dfn[N],T,fa[N];
int val[N],siz[N];
char s[N],ch;
queue<int>Q;
int read(){
    int fh=1,res=0;char ch;
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')fh=-1,ch=getchar();
    while(ch>='0'&&ch<='9')res=res*10+ch-'0',ch=getchar();
    return fh*res;
}
void write(int x){
    if(x>9)write(x/10);
    P(x%10+'0');
}
void lb(int x,int y){edge[++tot].to=y;edge[tot].next=head[x];head[x]=tot;}
int lowbit(int x){return x&(-x);}
void ins(int x,int delta){
    for(;x<=gs+1;x+=lowbit(x))c[x]+=delta;
}
int query(int x){
    int res=0;
    for(;x;x-=lowbit(x))res+=c[x];
    return res;
}
void dfs(int x){
    dfn[x]=++T;
    siz[x]=1;
    int i;
    for(i=head[x];i;i=edge[i].next)
        if(fa[x]!=edge[i].to){
            fa[edge[i].to]=x;
            dfs(edge[i].to);
            siz[x]+=siz[edge[i].to];
        }
}
bool cmp(note x,note y){return x.v<y.v||(x.v==y.v&&x.u<y.u);}
int main(){
    ch=getchar();
    while((ch>='a'&&ch<='z')||ch=='B'||ch=='P')s[++l]=ch,ch=getchar();
    n=read();
    fo(i,1,n){
        u=read(),v=read();
        qu[i].id=i;qu[i].u=u;qu[i].v=v;
    }
    k=0;gs=0;pre[0]=-1;
    fo(i,1,l){
        ch=s[i];
        if(ch>='a'&&ch<='z'){
            x=s[i]-'a';
            if(tr[k][x])k=tr[k][x];
                else{
                    tr[k][x]=++gs;
                    val[gs]=x;
                    pre[gs]=k;
                    k=gs;
                }
        } else
        if(ch=='B')k=pre[k];else bz[++cs]=k;
    }
    fail[0]=-1;
    Q.push(0);
    while(!Q.empty()){
        x=Q.front();Q.pop();
        fo(i,0,25){
            j=tr[x][i];
            if(!j)continue;
            Q.push(j);
            k=fail[x];
            while(~k&&!tr[k][i])k=fail[k];
            if(k==-1)fail[j]=0;else fail[j]=tr[k][i];
        }
    }
    fo(i,1,gs)lb(fail[i],i);
    dfs(0);
    sort(qu+1,qu+n+1,cmp);
    k=0;y=1;
    ins(1,1);
    fo(i,1,l){
        ch=s[i];
        if(ch>='a'&&ch<='z'){
            x=s[i]-'a';
            k=tr[k][x];
            ins(dfn[k],1);
        } else
        if(ch=='B'){
            ins(dfn[k],-1);
            k=pre[k];
        }
        else{
            wz++;
            while(wz==qu[y].v && y<=n){
                u=bz[qu[y].u];
                ans[qu[y].id]=query(dfn[u]+siz[u]-1)-query(dfn[u]-1);
                y++;
            }
        }
    }
    ins(1,-1);
    fo(i,1,n)write(ans[i]),P('\n');
    return 0;
}
  • 2
    点赞
  • 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、付费专栏及课程。

余额充值