WOJ#3831 TJOI2013单词
题面
某人读论文,一篇论文是由许多单词组成。但他发现一个单词会在论文中出现很多次,现在想知道每个单词分别在论文中出现多少次。
输入
第一个一个整数 N ,表示有多少个单词,接下来 N 行每行一个单词。
输出
输出 N 个整数,第 i 行的数字表示第 i 个单词在文章中出现了多少次。
样例输入
3
a
aa
aaa
样例输出
6
3
1
提示
对于全部数据,1≤N≤200 ,所有单词长度的和不超过 106,保证每个单词由小写字母组成。
题解
本题中,第2行到第N+1行构成论文,同时每一行也是单词,所以我们可以用AC自动机求解,依次用每一个单词进行匹配,每次匹配到一个单词的结尾时,就在ans数组中对应这个结尾的单词序号的位置记录答案。
但是想一想就会发现上述方法不但会超时,还可能在单词出现重复时出错(Trie树上记录单词结尾时只能记录一个单词的序号,遇到重复时重复单词会因此得到0,笔者最初就是因为没考虑这一点而错误)。因此我们可以用一个sme数组来记录每一种单词的序号,同时在Trie树上记录单词结尾时记录这种单词的序号。再进一步思考可以发现,如果我们把每个单词的重复次数用tim数组记录下来,就可以避免因重复查询带来的超时。
代码
#include<bits/stdc++.h> using namespace std; #define N 1000010 int n,tot,ans[N],nxt[N],sme[N],tim[N],vis[N],ch[N][30]; char CH[210][50010]; void build(char *s,int ord){ int u=1,len=strlen(s); for(int i=0;i< len;i++){ int c=s[i]-‘a‘; if(!ch[u][c]){ch[u][c]=++tot;memset(ch[tot],0,sizeof(ch[tot]));} u=ch[u][c]; } if(!vis[u]){vis[u]=ord;sme[ord]=ord;tim[ord]=1;} else{sme[ord]=vis[u];tim[vis[u]]++;} return ; } void bfs(){ queue<int>q; for(int i=0;i<=25;i++) ch[0][i]=1; q.push(1);nxt[1]=0; while(!q.empty()){ int u=q.front();q.pop(); for(int i=0;i<=25;i++){ if(!ch[u][i]){ch[u][i]=ch[nxt[u]][i];continue;} q.push(ch[u][i]); int v=nxt[u];nxt[ch[u][i]]=ch[v][i]; } } } void find(char *s,int num){ int u=1,len=strlen(s); for(int i=0;i<=len;i++){ int c=s[i]-‘a‘,k=ch[u][c]; while(k>1){ if(vis[k]) ans[vis[k]]+=num; k=nxt[k]; } u=ch[u][c]; } return ; } int main(){ scanf("%d",&n);tot=1; for(int i=1;i<=n;i++){scanf("%s",CH[i]);build(CH[i],i);} bfs(); for(int i=1;i<=n;i++){if(sme[i]==i) find(CH[i],tim[i]);} for(int i=1;i<=n;i++){printf("%d\n",ans[sme[i]]);} return 0; }
原文地址:https://www.cnblogs.com/doyo2019/p/10849643.html
时间: 2024-10-21 14:06:06