【Coel.解题报告】[TJOI2013]单词
题前碎语
这篇报告和上一篇是同一天写的,所以没什么好说的。
直接进入正题。
题目梗概:[TJOI2013]单词
题目描述
小张最近在忙毕设,所以一直在读论文。一篇论文是由许多单词组成但小张发现一个单词会在论文中出现很多次,他想知道每个单词分别在论文中出现了多少次。
输入格式
第一行一个整数 \(N\),表示有 \(N\) 个单词。
接下来 \(N\) 行每行一个单词,每个单词都由小写字母 \(a-z\) 组成。
输出格式
输出 \(N\) 个整数,第 \(i\) 行的数表示第 \(i\) 个单词在文章中出现了多少次。
看起来是多模式串匹配,想当然地开一个\(AC\)自动机
但是这题有相同单词,所以直接建\(Trie\)树会把相同的树覆盖,答案就错了。
可以利用\(Fail\)数组的特性,给每个节点加一个权值表示相同字符串数量。
这样,在计算的时候只需要把每个相对应的数量相加即可。
代码如下:
#include<cstdio>
#include<cstring>
#include<queue>
#define maxn 1000050
#define maxc 26
using namespace std;
int n,cnt=1,tr[maxn][maxc],fail[maxn],vis[maxn],ans[maxn],ord[maxn];
char s[maxn];
queue<int>Q;
inline void insert(char *s,int x)
{
int u=0,len=strlen(s);
for(register int i=0;i<len;i++)
{
char c=s[i]-'a';
if(!tr[u][c])
tr[u][c]=cnt++;
u=tr[u][c];
ans[u]++;//节点权值
}
vis[x]=u;//第i个数组对应的节点
}
inline void getfail()
{
cnt=0;//把建树时的cnt做一个重复利用
for(register int i=0;i<maxc;i++)
if(tr[0][i])Q.push(tr[0][i]);
while(!Q.empty())
{
int u=Q.front();
Q.pop();
ord[++cnt]=u;
for(register int i=0;i<maxc;i++)
{
if(tr[u][i])
{
fail[tr[u][i]]=tr[fail[u]][i];
Q.push(tr[u][i]);
}
else tr[u][i]=tr[fail[u]][i];
}
}
}
inline void query()
{
for(register int i=cnt;i;i--)
ans[fail[ord[i]]]+=ans[ord[i]];//非常简短的query
}
inline void write(int x)//考虑到输出量会非常大,写一个快写
{
if(x>9)write(x/10);
putchar(x%10+'0');
}
int main()
{
scanf("%d",&n);
for(register int i=1;i<=n;i++)
{
scanf("%s",s);
insert(s,i);
}
getfail();
query();
for(register int i=1;i<=n;i++)
write(ans[vis[i]]),puts("");//注意快写不能输出换行,用puts来补充
}
题后闲话
机房20:05要强制关机,有点难受。
今天就写到这里吧。