SMOJ 2019 歌词 (AC自动机)

题目描述

C同学很喜欢唱歌。唱了n首歌后他发现有一些歌词在这n首歌中经常出现。比如:
《两只蝴蝶》
亲爱的你慢慢飞
小心前面带刺的玫瑰
亲爱的你张张嘴
风中花香会让你沉醉
……
《风雨彩虹铿锵玫瑰》
一切美好只是昨日沉醉
淡淡苦涩才是今天滋味
想想明天又是雨晒风吹
再苦再累无惧无畏
身上的痛让我难以入睡
脚下的路还有更多的累
追逐梦想总是百转千回
无怨无悔从容面对
风雨彩虹 铿锵玫瑰
再多忧伤再多痛苦自己去背
风雨彩虹 铿锵玫瑰
……
在上述两首歌的片段中,玫瑰总共出现了3次,风总共出现了4次。
C同学列出了w个他认为经常出现的歌词,他想知道每个歌词在这n首歌中出现了多少次。


输入格式

第一行一个整数w。
接下来w行,每行一个字符串,第i行表示第i个歌词。
接下来一个整数n。
接下来n行,每行一个字符串,第i行表示第i首歌。
字符串由大小写字母,数字与”-“组成。


输出格式

一共w行,每行一个整数,第i行表示第i个歌词在这n首歌中出现了多少次。


数据范围

这里写图片描述


输入样例

【样例一输入】

5
he
she
sher
his
hers
2
ushers
she-said-he-said-she-said-he-said-his

【样例二输入】

3
who
shawty
hawt
2
Get-it-shawty-Get-it-shawty
Whoa-W-W-Whoa-Shawtyyyyy

样例三输入:

1
aa
1
aaa


输出样例

【样例一输出】

5
3
1
1
1

【样例二输出】

0
2
3

样例三输出:

2


题解

本题就是一道AC自动机的果题。然而我太久太久没码过AC机(到是天天去),做的时候码了半天,一运行立刻停止工作,我掏出以前的模板,发现自己打错了一些小细节。最后千辛万苦过了样例,交上去只有90分。原来这题歌词可能重复出现,于是我们就再开个数组记一下就行了。

吹B吹了好久,忘了讲做法了。就是对短串建AC机,然后用长串在上面跑,经过一个点匹配的话,就代表沿着fail指针一路跳都是匹配的(都是后缀),于是我们只要建一棵fail树,由fail指针向自己连边,最后dfs一边fail树,将权值和向上加就可以了。这样是严格线性的。然而我写的代码是每次慵懒地向上沿fail指针匹配,这样理论最坏可能到二次方级别,不过数据是没有那样刁钻的。

最后,此题空间512MB,AC机理论上可能开到250w,但是实际比这要小得多,所以能开多少是多少,最后A了就好。KsCla还有超级厉害的SAM作法,但是本蒟蒻还是不会SAM,又没时间去学(要是可以不上文化课就有时间了),所以SAM这个坑就以后再填吧。


代码

#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <iostream>
#include <cstring>
#include <algorithm>
#define MAXL 50002
#define MAXN 502
#define MAXM 1000000

using namespace std;

int w, n, cnt, Ans[MAXN], len, p, name[MAXN];

char s[MAXL];

struct AC{
    AC *son[63], *fail;
    int id;
    void Clear(){
        id = 0;
        for(int i = 0; i < 63; i++)
            son[i] = NULL;
    }
}Node[MAXM], *q[MAXM], *Root, *now, *temp;

AC *NewTnode(){
    Node[cnt].Clear();
    return Node+cnt++;
}

int Get_pos(char x){
    if(x >= 'a' && x <= 'z')  return x - 'a';
    if(x >= 'A' && x <= 'Z')  return x - 'A' + 26;
    if(x >= '0' && x <= '9')  return x - '0' + 52;
    return 62;
}

void AC_Insert(char *x, int id){
    now = Root;
    len = strlen(x);
    for(int i = 0; i < len; i++){
        p = Get_pos(x[i]);
        if(!now->son[p])  now->son[p] = NewTnode();
        now = now->son[p];
    }
    name[id] = id;
    if(now->id)  name[id] = now->id;
    else  now->id = id;
}

void AC_Build(){
    int head, tail;
    q[head = tail = 0] = Root;
    Root->fail = NULL;
    while(head <= tail){
        now = q[head++];
        for(int i = 0; i < 63; i++)  if(now->son[i]){
            q[++tail] = now->son[i];
            now->son[i]->fail = Root;
            temp = now->fail;
            while(temp){
                if(temp->son[i]){
                    now->son[i]->fail = temp->son[i];//Saber
                    break;
                }
                temp = temp->fail;
            }
        }
    }
}

void AC_Find(char *x){
    now = Root;
    len = strlen(x);
    for(int i = 0; i < len; i++){
        p = Get_pos(x[i]);
        while(!now->son[p] && now != Root)  now = now->fail;//Saber
        if(!now->son[p])  continue;//Saber
        now = now->son[p];//Saber
        temp = now;
        while(temp){
            Ans[name[temp->id]] ++;
            temp = temp->fail;
        }
    }
}

int main(){

    Root = NewTnode();

    scanf("%d", &w);
    for(int i = 1; i <= w; i++){
        scanf("%s", &s);
        AC_Insert(s, i);
    }

    AC_Build();

    scanf("%d", &n);
    for(int i = 1; i <= n; i++){
        scanf("%s", &s);
        AC_Find(s);
    }

    for(int i = 1; i <= w; i++)
        printf("%d\n", Ans[name[i]]);
    return 0;
}

山有木兮木有枝,心悦君兮君不知。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值