链接:https://ac.nowcoder.com/acm/problem/20443
来源:牛客网
时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld
题目描述
某人读论文,一篇论文是由许多单词组成。但他发现一个单词会在论文中出现很多次,现在想知道每个单词分别在论文中出现多少次。
输入描述:
第一个一个整数N,表示有多少个单词,接下来N行每行一个单词。每个单词由小写字母组成,N ≤ 200,单词长度不超过10^6
输出描述:
输出N个整数,第i行的数字表示第i个单词在文章中出现了多少次。
示例1
输入
复制3 a aa aaa
3
a
aa
aaa
输出
6
3
1
题目分析:
卡了我好一会儿,最后错在单词可能重复,but最后T了。
看了一下题解发现妙哉!AC自动机fail树上跑dp即可。
假设考虑 一个字符串s在另一个字符串中出现的次数比如 样例中a在aaa中出现的次数,首先a可以作为前缀,所以构造aaa时第1个位置就需要+1,其次a可以作为aaa中的最长相同前后缀,比如a可以作为aa的最长相同前后缀,这就说明aa可以贡献一个a。那么问题又转换成了aa的出现次数,aa首先出现作为前缀一次,其次又是aaa的最长相同前后缀,所以为2次,说明aaa可以贡献两个aa,三个a。由此可以看出,这是个fail树上的dp,dp[i]代表fail树上以i结尾的单词在整个字典库中出现了多少次,那么按层最底层向第一层dp:sz[fail[u]]+=sz[u]。
原理就是一个串在另一个串中的出现次数可以由前缀+后缀次数组成。
这个可以举例自证:
aba 在abababa中的出现次数:
首先作为前缀出现一次。
之后ababa是最长相同前后缀,ababa就会出现2次,一次作为前缀一次作为后缀
之后aba是最ababa的长相同前后缀,同样的此时aba存在两次ababa的后缀所以后缀次数是2
过程中不会重复原因:
abababa->ababa(2)->aba(3)我们每次加入的都是上一个字符串后缀aba的数量,因为每次fail指针后,字符串长度会缩小,也就相当于终点会改变,所以不会重复。并且前缀的数量不会重复,因为aba不可能具有aba的最长相同前后缀!
/*** keep hungry and calm CoolGuang!***/
#pragma GCC optimize(2)
#include <bits/stdc++.h>
#include<stdio.h>
#include<algorithm>
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long long ll;
typedef pair<int,int> pp;
const ll INF=1e18;
const int maxn=1e6+18;
const int mod=19260817;
inline bool read(ll &num)
{char in;bool IsN=false;in=getchar();if(in==EOF) return false;while(in!='-'&&(in<'0'||in>'9')) in=getchar();if(in=='-'){ IsN=true;num=0;}else num=in-'0';while(in=getchar(),in>='0'&&in<='9'){num*=10,num+=in-'0';}if(IsN) num=-num;return true;}
ll n,m,p;
int res[maxn];
struct Acaho{
int nxt[maxn][26];
int rt=0;///
int id[maxn];
int cot[maxn];
int fail[maxn];
ll cnt;
int number=0;
void inint(){
cnt=0;
number=0;
memset(fail,0,sizeof(fail));
memset(id,0,sizeof(id));
memset(cot,0,sizeof(cot));
memset(nxt,0,sizeof(nxt));
memset(res,0,sizeof(res));
}
void Insert(char *p){
int len=strlen(p);
number++;
int rt=0;
for(int i=0;i<len;i++){
int x=p[i]-'a';
if(!nxt[rt][x]) nxt[rt][x]=++cnt;
rt=nxt[rt][x];
cot[rt]++;
}
res[number]=rt;
}
void GetFail(){
queue<int>q;
while(!q.empty()) q.pop();
fail[0]=-1;
q.push(0);
int y=0;
while(!q.empty()){
int u=q.front();q.pop();
for(int k=0;k<26;k++){
if(nxt[u][k]){
id[++y]=nxt[u][k];
int v=fail[u];
while(v!=-1&&nxt[v][k]==0) v=fail[v];
if(v==-1) fail[nxt[u][k]]=0;
else fail[nxt[u][k]]=nxt[v][k];
q.push(nxt[u][k]);
}
}
}
}
void Find(){
for(int i=cnt;i>=1;i--) cot[fail[id[i]]]+=cot[id[i]];
for(int i=1;i<=n;i++)
printf("%d\n",cot[res[i]]);
}
}aho;
char str[202][maxn];
int main()
{
aho.inint();
scanf("%lld",&n);
for(int i=1;i<=n;i++){
scanf("%s",str[i]);
aho.Insert(str[i]);
}
aho.GetFail();
aho.Find();
return 0;
}
/***
6
1 2
1 3
2 4
2 5
5 6
***/