题目大意:求一篇论文中每个单词分别在论文中出现多少次。

本题用AC自动机太慢,应该用Fail树将AC自动机中所有的Fail指针反向得到一个新树,这就是Fail树。对长度为x的字符串a和长度为y的字符串b,如果a是b的子串,则a可能与位于b[0,a],b[0,a+1],b[0,a+2]...b[0,y]中的后缀相等。根据fail指针的定义,只要沿着反向Fail边走,走到的节点所代表的字符串必然存在与a(前缀)相等的后缀。因此,一遍DFS,返回加上子节点的总Cnt值的当前节点的Cnt值,即可。注意,Trie树中,有些节点是多个字符串公用的,因此每次构造Trie树时,都要对每个节点的Cnt++,以等价于此处存在多个字符串。

#include <cstdio>
#include <cstring>
#include <cassert>
#include <algorithm>
#include <cmath>
#include <queue>
#include <vector>
using namespace std; const int MAX_CHAR = , MAX_LEN = 1e6 + , MAX_STR = ; struct FailTree
{
#define Root _nodes[0]
#define Org(x) x - 'a' struct Node;
struct Edge; struct Node
{
Node *Next[MAX_CHAR], *Fail;
int Cnt;
Edge *Head;
Node() :Cnt(), Fail(NULL), Head(NULL) { memset(Next, NULL, sizeof(Next)); }
};
vector<Node*> _nodes, Tail; struct Edge
{
Node *To;
Edge *Next;
Edge(Node *to, Edge *next):To(to),Next(next){}
};
vector<Edge*> _edges; FailTree()
{
_nodes.push_back(new Node());
} void AddEdge(Node *from, Node *to)
{
Edge *e = new Edge(to, from->Head);
from->Head = e;
_edges.push_back(e);
} Node *BuildTrie(char *s)
{
int len = strlen(s);
Node *cur = Root;
for (int i = ; i < len; i++)
{
if (!cur->Next[Org(s[i])])
_nodes.push_back(cur->Next[Org(s[i])] = new Node());
cur = cur->Next[Org(s[i])];
cur->Cnt++;
}
return cur;
} void Insert(char *s)
{
Tail.push_back(BuildTrie(s));
} void SetFail()
{
static queue<Node*> q;
q.push(Root);
while (!q.empty())
{
Node *cur = q.front();
q.pop();
for (int i = ; i < MAX_CHAR; i++)
{
if (cur->Next[i])
{
Node *temp = cur->Fail;
while (temp)
{
if (temp->Next[i])
{
cur->Next[i]->Fail = temp->Next[i];
AddEdge(temp->Next[i], cur->Next[i]);
break;
}
temp = temp->Fail;
}
if (!temp)
{
cur->Next[i]->Fail = Root;
AddEdge(Root, cur->Next[i]);
}
q.push(cur->Next[i]);
}
}
}
} int Dfs(Node *u)
{
for (Edge *e = u->Head; e; e = e->Next)
u->Cnt += Dfs(e->To);
return u->Cnt;
}
}g; int main()
{
#ifdef _DEBUG
freopen("c:\\noi\\source\\input.txt", "r", stdin);
#endif
int tot;
char s[MAX_LEN];
scanf("%d", &tot);
for(int i=; i<tot; i++)
{
scanf("%s", s);
g.Insert(s);
}
g.SetFail();
g.Dfs(g.Root);
for (int i = ; i < tot; i++)
printf("%d\n", g.Tail[i]->Cnt);
return ;
}

或者不用反向Fail指针也可以,站在后缀上去找其所包含的前缀。这样编程复杂度低一些。

#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
#include <cassert>
#include <cmath>
#include <algorithm>
using namespace std; const int MAX_CHAR = , MAX_NODE = 5e5 + , MAX_LEN = 1e6 + ; struct Node
{
int Sum, Id, Cnt;
Node *Fail;
Node *Next[MAX_CHAR];
}Nodes[MAX_NODE];
int Nodes_Cnt = ;
char P[MAX_LEN];
Node *WordNode[MAX_NODE]; int Ord(char c)
{
return c - 'a';
} Node *NewNode()
{
return ++Nodes_Cnt + Nodes;
} Node *Root()
{
return Nodes + ;
} void BuildTrie(char *s, int id)
{
Node *cur = Root();
int len = strlen(s);
for (int i = ; i < len; i++)
{
if (cur->Next[Ord(s[i])])
cur = cur->Next[Ord(s[i])];
else
cur = cur->Next[Ord(s[i])] = NewNode();
}
cur->Sum++;
cur->Id = id;
WordNode[id] = cur;
} void SetFail()
{
queue<Node*> q;
q.push(Root());
while (!q.empty())
{
Node *cur = q.front();
q.pop();
for (int i = ; i < MAX_CHAR; i++)
{
if (cur->Next[i])
{
Node *temp = cur->Fail;
while (temp)
{
if (temp->Next[i])
{
cur->Next[i]->Fail = temp->Next[i];
break;
}
temp = temp->Fail;
}
if (!temp)
{
cur->Next[i]->Fail = Root();
}
q.push(cur->Next[i]);
}
}
}
} int Dfs1(Node *cur)
{
int cnt = cur->Sum;
for (int i = ; i < MAX_CHAR; i++)
if (cur->Next[i])
cnt += Dfs1(cur->Next[i]);
for (Node *temp = cur; temp != Root(); temp = temp->Fail)
if (temp->Sum)
temp->Cnt+=cnt;
//cur->Cnt += cnt;
return cnt;
} int main()
{
//freopen("c:\\noi\\source\\input.txt", "r", stdin);
int totP;
scanf("%d", &totP);
for (int i = ; i < totP; i++)
{
scanf("%s", P);
BuildTrie(P, i);
}
SetFail();
Dfs1(Root());
for (int i = ; i < totP; i++)
printf("%d\n", WordNode[i]->Cnt);
return ;
}

BZOJ3172 单词 Fail树的更多相关文章

  1. 【BZOJ2905】背单词 fail树+DFS序+线段树

    [BZOJ2905]背单词 Description 给定一张包含N个单词的表,每个单词有个价值W.要求从中选出一个子序列使得其中的每个单词是后一个单词的子串,最大化子序列中W的和. Input 第一行 ...

  2. bzoj 3172: [Tjoi2013]单词 fail树

    题目大意: 一篇论文是由许多单词组成,现在想知道每个单词分别在论文中出现多少次. 题解: 我们首先考虑fail指针的含义 如果fail[x] = y,那么我们就知道y作为x的后缀在x中出现了一次 所以 ...

  3. BZOJ3172[Tjoi2013]单词——AC自动机(fail树)

    题目描述 某人读论文,一篇论文是由许多单词组成.但他发现一个单词会在论文中出现很多次,现在想知道每个单词分别在论文中出现多少次. 输入 第一个一个整数N,表示有多少个单词,接下来N行每行一个单词.每个 ...

  4. BZOJ3172 & 洛谷3966 [Tjoi2013]单词 【fail树】

    3172: [Tjoi2013]单词 Time Limit: 10 Sec  Memory Limit: 512 MB Submit: 4293  Solved: 2083 [Submit][Stat ...

  5. [Bzoj3172][Tjoi2013]单词(fail树)

    3172: [Tjoi2013]单词 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 4777  Solved: 2345[Submit][Status ...

  6. BZOJ 3172: [Tjoi2013]单词 [AC自动机 Fail树]

    3172: [Tjoi2013]单词 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 3198  Solved: 1532[Submit][Status ...

  7. bzoj 3172 [Tjoi2013]单词(fail树,DP)

    [题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=3172 [题意] 题目的意思是这样的,给若干个单词,求每个单词在这一堆单词中的出现次数. ...

  8. BZOJ2905: 背单词 AC自动机+fail树+线段树

    $zjq$神犇一眼看出$AC$自动机 $Orz$ 直接就讲做法了 首先对每个串建出$AC$自动机 将$fail$树找到 然后求出$dfs$序 我们发现一个单词 $S_i$是$S_j$的子串当且仅当$S ...

  9. 【洛谷】3966:[TJOI2013]单词【AC自动机】【fail树】

    P3966 [TJOI2013]单词 题目描述 小张最近在忙毕设,所以一直在读论文.一篇论文是由许多单词组成但小张发现一个单词会在论文中出现很多次,他想知道每个单词分别在论文中出现了多少次. 输入输出 ...

随机推荐

  1. P1146 硬币翻转

    题目描述 在桌面上有一排硬币,共N枚,每一枚硬币均为正面朝上.现在要把所有的硬币翻转成反面朝上,规则是每次可翻转任意N-1枚硬币(正面向上的被翻转为反面向上,反之亦然).求一个最短的操作序列(将每次翻 ...

  2. 小程序开发之搭建WebSocket的WSS环境(Apache+WorkerMan框架+PHP)

    最近公司的一个IoT项目用到了小程序的WSS协议环境,现在把整个的搭建开发过程分享给大家. 这里我们用的是WorkerMan框架,服务器是CentOS,Web服务器是Apache,开发语言是PHP. ...

  3. android黑科技系列——爆破一款应用的签名验证问题

    一.前言 在之前的文章中说过Android中的安全和破解是相辅相成的,为了防止被破解,很多应用做了一些防护策略,但是防护策略也是分等级,一般简单的策略就是混淆代码和签名校验,而对于签名校验很多应用都是 ...

  4. Android_传感器光学

    上一篇写了一个小案例方向传感器,与这光学传感器原理大致类似,但其实代码的主要区别得到的类型不一样在这里我一一列举出来: * Sensor.TYPE_ORIENTATION:方向传感器. * Senso ...

  5. STL之map篇

    度熊所居住的 D 国,是一个完全尊重人权的国度.以至于这个国家的所有人命名自己的名字都非常奇怪.一个人的名字由若干个字符组成,同样的,这些字符的全排列的结果中的每一个字符串,也都是这个人的名字.例如, ...

  6. 迭代器与index遍历

    迭代器用于链式组织的序列. index用于线性组织的序列.

  7. Docker系列之入门

    Docker基本介绍 一.什么是Docker 在docker的官方之什么是docker中提到了一句话:“当今各大组织或者团体的创新都源于软件(例如OA.ERP等),其实很多公司都是软件公司" ...

  8. 【udacity】机器学习-神经网络

    Evernote Export 1.神经网络 神经元 细胞的主体称为细胞体,然后有轴突.突触 他们构建的方式是可以调整的 我们会有一些输入的放电信号视为放电频率或输入的强度 X1​w1​X2​w2​X ...

  9. wafII笔记

    wafII笔记:    组件的使用方法:        组件属性:                 属性的设置和获取通过option方法来完成 waf("#id").wafProm ...

  10. ArchLinux简单介绍

    一.Archlinux的由来 2002年由加拿大的Judd Vinet,Archlinux的创始人 怀着对Debian.Redhat的包管理器不满,于是创建了Archlinux!目前ArchLinux ...