https://vjudge.net/problem/LightOJ-1427

把所有模式串加入ac自动机,然后search的时候暴力,每个子串都暴力一下就好。

其实AC自动机就是,先建立好trie图。预处理加速查找

然后查找有多少个模式串的时候,相当于一个暴力,

每一次循环,其实就是枚举文本串的每一个位置,以它为结尾的子串中,有多少个出现在模式串中。

直接做是要枚举每一个模式串,AC自动机就把这个步骤简化为Fail指针了。用fail指针查找。

相当于,查找str[1...i]   str[2...i] ,  str[3....i].....srt[i, i]是否在模式串中

#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false)
using namespace std;
#define inf (0x3f3f3f3f)
typedef long long int LL;
typedef unsigned long long int ULL;
const int maxn = 5e2 + ;
char sub[maxn][maxn], str[ + ];
int len[maxn];
const int N = ;
struct node {
int flag;
struct node *Fail; //失败指针,匹配失败,跳去最大前后缀
struct node *pNext[N];
} tree[maxn * maxn];
int t; //字典树的节点
struct node *create() { //其实也只是清空数据而已,多case有用,根是0号顶点、
struct node *p = &tree[t++];
p->flag = ;
p->Fail = NULL;
for (int i = ; i < N; i++) {
p->pNext[i] = NULL;
}
return p;
}
void insert(struct node **T, char str[], int id) {
struct node *p = *T;
if (p == NULL) {
p = *T = create();
}
for (int i = ; str[i]; i++) {
int id = str[i] - 'a';
if (p->pNext[id] == NULL) {
p->pNext[id] = create();
}
p = p->pNext[id];
}
p->flag = id; //相同的单词算两次
}
void BuiltFail(struct node **T) {
//根节点没有失败指针,所以都是需要特判的
//思路就是去到爸爸的失败指针那里,找东西匹配,这样是最优的
struct node *p = *T; //用个p去代替修改
struct node *root = *T;
if (p == NULL) return ;
//树上bfs,要更改的是p->pNext[i]->Fail
struct node *que[t + ]; //这里的t是节点总数,字典树那里统计的,要用G++编译
int head = , tail = ;
que[tail++] = root;
while (head < tail) {
p = que[head]; //p取出第一个元素 ★
for (int i = ; i < N; i++) { //看看存不存在这个节点
if (p->pNext[i] != NULL) { //存在的才需要管失败指针。
if (p == root) { //如果爸爸是根节点的话,根节点没有失败指针
p->pNext[i]->Fail = root; //指向根节点
} else {
struct node *FailNode = p->Fail; //首先找到爸爸的失败指针
while (FailNode != NULL) {
if (FailNode->pNext[i] != NULL) { //存在
p->pNext[i]->Fail = FailNode->pNext[i];
break;
}
FailNode = FailNode->Fail; //回溯,根节点的fail是NULL
}
if (FailNode == NULL) { //如果还是空,那么就指向根算了
p->pNext[i]->Fail = root;
}
}
que[tail++] = p->pNext[i]; //这个id是存在的,入队bfs
} else if (p == root) { //变化问题,使得不存在的边也建立起来。
p->pNext[i] = root;
} else {
p->pNext[i] = p->Fail->pNext[i]; //变化到LCP。可以快速匹配到病毒。
//就是在p这个节点上,再增加一个点pNext[i],就是不合法串。
}
}
head++;
}
}
ULL val[maxn];
int ans[maxn];
void calc(struct node *T) {
struct node * p = T;
struct node * root = T;
if (p == NULL) return;
for (int i = ; str[i]; ++i) {
int id = str[i] - 'a';
p = p->pNext[id];
struct node *temp = p;
while (temp != root) {
if (temp->flag) ans[temp->flag]++;
temp = temp->Fail;
}
}
}
void work() {
t = ;
int n;
scanf("%d", &n);
scanf("%s", str + );
int lenstr = strlen(str + );
struct node *T = NULL;
for (int i = ; i <= n; ++i) {
scanf("%s", sub[i] + );
len[i] = strlen(sub[i] + );
insert(&T, sub[i], i);
ULL fuck = ;
for (int j = ; j <= len[i]; ++j) {
fuck = fuck * + sub[i][j];
}
val[i] = fuck;
}
BuiltFail(&T);
memset(ans, false, sizeof ans);
calc(T);
// printf("%d\n", val[1] == val[3]);
for (int i = ; i <= n; ++i) {
for (int j = i + ; j <= n; ++j) {
if (val[i] == val[j]) ans[i] = ans[j] = max(ans[i], ans[j]);
}
}
static int f = ;
printf("Case %d:\n", ++f);
for (int i = ; i <= n; ++i) {
printf("%d\n", ans[i]);
}
} int main() {
#ifdef local
freopen("data.txt", "r", stdin);
// freopen("data.txt", "w", stdout);
#endif
int t;
scanf("%d", &t);
while (t--) work();
return ;
}

其实sam每一次也就O(lensub)复杂度,所以总复杂度是500 * 500的

但是不行,MLE

烦。感觉sam被卡内存很严重

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <queue>
#include <algorithm>
#define IOS ios::sync_with_stdio(false)
using namespace std;
#define inf (0x3f3f3f3f)
typedef long long int LL;
const int maxn = 2e6 + , N = ;
struct Node {
int mxCnt; //mxCnt表示后缀自动机中当前节点识别子串的最大长度
int miCnt; //miCnt表示后缀自动机中当前节点识别子串的最小长度
int id; //表示它是第几个后缀自动机节点,指向了它,但是不知道是第几个,用id判断
bool flag; //表示当前节点是否能识别前缀
struct Node *pNext[N], *fa;
}suffixAutomaton[maxn], *root, *last; //大小需要开2倍,因为有一些虚拟节点
int t; //用到第几个节点
struct Node *create(int mxCnt = -, struct Node *node = NULL) { //新的节点
if (mxCnt != -) {
suffixAutomaton[t].mxCnt = mxCnt, suffixAutomaton[t].fa = NULL;
for (int i = ; i < N; ++i) suffixAutomaton[t].pNext[i] = NULL;
} else {
suffixAutomaton[t] = *node; //保留了node节点所有的指向信息
//可能需要注意下pos,在原串中的位置。现在pos等于原来node的pos
}
suffixAutomaton[t].id = t; //必须要有的,不然id错误
suffixAutomaton[t].flag = false;
return &suffixAutomaton[t++];
}
void addChar(int x, int pos) { //pos表示在原串的位置
struct Node *p = last, *np = create(p->mxCnt + , NULL);
np->flag = true;
last = np; //last是最尾那个可接收后缀字符的点。
for (; p != NULL && p->pNext[x] == NULL; p = p->fa) p->pNext[x] = np;
if (p == NULL) {
np->fa = root;
np->miCnt = ; // 从根节点引一条边过来
return;
}
struct Node *q = p->pNext[x];
if (q->mxCnt == p->mxCnt + ) { //中间没有任何字符
np->fa = q;
np->miCnt = q->mxCnt + ; // q是7-->8的那些"ab",np是"bab"长度是2+1
return;
}
// p: 当前往上爬到的可以接受后缀的节点
// np:当前插入字符x的新节点
// q: q = p->pNext[x],q就是p中指向的x字符的节点
// nq:因为q->cnt != p->cnt + 1而新建出来的模拟q的节点
struct Node *nq = create(-, q); // 新的q节点,用来代替q,帮助np接收后缀字符
nq->mxCnt = p->mxCnt + ; //就是需要这样,这样中间不包含任何字符
q->miCnt = nq->mxCnt + , np->miCnt = nq->mxCnt + ;
q->fa = nq, np->fa = nq; //现在nq是包含了本来q的所有指向信息
for (; p && p->pNext[x] == q; p = p->fa) {
p->pNext[x] = nq;
}
}
void init() {
t = ;
root = last = create(, NULL);
}
void build(char str[], int lenstr) {
init();
for (int i = ; i <= lenstr; ++i) {
addChar(str[i] - 'a', i);
}
}
char str[maxn];
int lenstr;
int in[maxn];
int dp[maxn];
int que[maxn];
int ans[maxn];
char sub[maxn];
const int MOD = 1e9 + ;
void work() {
int n;
scanf("%d", &n);
scanf("%s", str + );
lenstr = strlen(str + );
build(str, lenstr);
for (int i = ; i < t; ++i) {
in[suffixAutomaton[i].fa->id]++;
if (suffixAutomaton[i].flag) dp[i] = ;
else dp[i] = ;
}
int head = , tail = ;
for (int i = ; i < t; ++i) {
if (in[i] == ) que[tail++] = i;
}
while (head < tail) {
int cur = que[head++];
if (!cur) break;
dp[suffixAutomaton[cur].fa->id] += dp[cur];
in[suffixAutomaton[cur].fa->id]--;
if (in[suffixAutomaton[cur].fa->id] == ) que[tail++] = suffixAutomaton[cur].fa->id;
}
static int f = ;
printf("Case %d:\n", ++f);
dp[] = ;
while (n--) {
scanf("%s", sub + );
int now = ;
for (int i = ; sub[i]; ++i) {
if (!suffixAutomaton[now].pNext[sub[i] - 'a']) {
now = ;
break;
}
now = suffixAutomaton[now].pNext[sub[i] - 'a']->id;
}
printf("%d\n", dp[now]);
}
}
int main() {
#ifdef local
freopen("data.txt", "r", stdin);
// freopen("data.txt", "w", stdout);
#endif
int t;
scanf("%d", &t);
while (t--)
work();
return ;
}

Substring Frequency (II) LightOJ - 1427 AC自动机的更多相关文章

  1. lightoj 1427 - Substring Frequency (II) AC自动机

    模板题,找来测代码. 注意有相同单词 //#pragma comment(linker, "/STACK:1024000000,1024000000") #include<c ...

  2. light oj 1427(ac自动机)

    #include <bits/stdc++.h> using namespace std; *; ; map<string,int>Map; struct Trie { int ...

  3. Codeforces 1015F Bracket Substring AC自动机 + dp

    Bracket Substring 这么垃圾的题怎么以前都不会写啊, 现在一眼怎么就会啊.... 考虑dp[ i ][ j ][ k ][ op ] 表示 已经填了 i 个空格, 末尾串匹配到 所给串 ...

  4. UVA11468 Substring --- AC自动机 + 概率DP

    UVA11468 Substring 题目描述: 给定一些子串T1...Tn 每次随机选择一个字符(概率会给出) 构造一个长为n的串S,求T1...Tn不是S的子串的概率 直接把T1...Tn建成AC ...

  5. UVA-11468 Substring(AC自动机+DP)

    题目大意:给一些模板串,一些字符的出现概率.问不会出现模板串的概率是多少. 题目分析:是比较简单的概率DP+AC自动机.利用全概率公式递推即可. 代码如下: # include<iostream ...

  6. UVa 11468 (AC自动机 概率DP) Substring

    将K个模板串构成一个AC自动机,那些能匹配到的单词节点都称之为禁止节点. 然后问题就变成了在Tire树上走L步且不经过禁止节点的概率. 根据全概率公式用记忆化搜索求解. #include <cs ...

  7. Codeforces963C Frequency of String 【字符串】【AC自动机】

    题目大意: 给一个串s和很多模式串,对每个模式串求s的一个最短的子串使得这个子串中包含至少k个该模式串. 题目分析: 均摊分析,有sqrt(n)种长度不同的模式串,所以有关的串只有msqrt(n)种. ...

  8. 沉迷AC自动机无法自拔之:[UVA 11468] Substring

    图片加载可能有点慢,请跳过题面先看题解,谢谢 这个鬼题目,上一波套路好了 先用题目给的模板串建\(AC\)自动机,把单词结尾标记为 \(val=1\),然后在建好的\(AC\)自动机上跑 \(dp\) ...

  9. UVa 11468 Substring (AC自动机+概率DP)

    题意:给出一个字母表以及每个字母出现的概率.再给出一些模板串S.从字母表中每次随机拿出一个字母,一共拿L次组成一个产度为L的串, 问这个串不包含S中任何一个串的概率为多少? 析:先构造一个AC自动机, ...

随机推荐

  1. 图测试题部分总结.ing

    一个无向连通图的生成树是含有该连通图的全部顶点的(极小连通子图) 在有向图G的拓扑序列中,若顶点Vi在顶点Vj之前,则下列情形不可能出现的是(D)A.G中有弧<Vi,Vj> B.G中有一条 ...

  2. .replace(/-/g,"/")的用法

    /-/g正则表达式   g  代表  global    全部替换 var str1 ="2012-08-12 23:13"; str1 = str1.replace(/-/g,& ...

  3. android自定义视图属性(atts.xml,TypedArray)学习

    是一个用于存放恢复obtainStyledAttributes(AttributeSet, int[], int, int)或 obtainAttributes(AttributeSet, int[] ...

  4. IDEA内嵌Jetty启动SpringMvc项目

    这段时间本意是想要研究一下Netty的多线程异步NIO通讯框架,看完原理想要做下源码分析.查找资料发现Jetty框架底层支持用Netty做web请求的多线程分发处理,于是就筹备着将Jetty框架内嵌到 ...

  5. java 字符流 字节流

    java对文本文档进行操作(拷贝.显示)出现乱码一般来说,可以从两个方面入手. 1.文本文件本身的编码格式. 2.java代码中处理文本文件的编码格式. 这里要注意的一点是,我们可以看出copyFil ...

  6. MVC下为什么要使用Areas

    想研究一下这个Areas,在博客园知识库找到这篇文章,先全部搬过来吧,原文地址:http://kb.cnblogs.com/page/144561/ 为什么需要分离? 我们知道MVC项目各部分职责比较 ...

  7. 在Android中使用FlatBuffers(中篇)

    本文来自网易云社区. FlatBuffers.Protobuf及JSON对比测试 FlatBuffers相对于Protobuf的表现又如何呢?这里我们用数据说话,对比一下FlatBuffers格式.J ...

  8. git 增量打包

    git diff f506693 ccc253c3 --name-only | xargs tar -czvf update.tar.gz

  9. loj#6435. 「PKUSC2018」星际穿越(倍增)

    题面 传送门 题解 我们先想想,在这个很特殊的图里该怎么走最短路 先设几个量,\(a_i\)表示\([a_i,i-1]\)之间的点都和\(i\)有边(即题中的\(l_i\)),\(l\)表示当前在计算 ...

  10. html二

    超链接 超链接有三种形式: 1.外部链接:链接到外部文件.举例: <a href="new.html">点击进入到新网页</a> a是英语anchor“锚” ...