【题目大意】
某人学习单词,有2中操作。1:+w,这个人学些了单词w;2:?p,这个人阅读一篇文章p。对于每次阅读,输出其中学习了的单词数,即字符串p中有多少个子串是出现过的。特别地,为了使你的算法是在线的,输入的所有串是加密的,如果前一个答案为L,实际的串是输出的串左移L次的那个串,默认初始答案为L。
【思路】
如果只是一个询问,那么做法很简单,就是很裸的AC自动机。但是不断有1,2操作的时候呢?如果每次都重构AC自动机,因为构造fail指针是需要把字典树bfs一次的,效率太差。这个时候,想到一个分块思想,另 limit = sqrt(10^5),我们构造两个AC自动机,每次把学习的字母加到后一个AC自动机中,如果后者的串总长>=limit,就是后一个AC自动机合到第一个中。
这样的话,查询的效率O(sum(p))。构造AC自动机的效率,显然,合并操作不会超过sqrt(10^5)次,一次合并效率最坏为 10^5 ,而每次后一个AC自动机重构fail指针的效率总和最坏也不会超过n*sqrt(n)。
#pragma comment(linker, "/STACK:102400000,102400000")
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<cmath>
#include<cctype>
#include<string>
#include<algorithm>
#include<iostream>
#include<ctime>
#include<map>
#include<set>
using namespace std;
#define MP(x,y) make_pair((x),(y))
#define PB(x) push_back(x)
typedef __int64 LL;
//typedef unsigned __int64 ULL;
/* ****************** */
const int INF=100011122;
const double INFF=1e100;
const double eps=1e-8;
const int mod=1000000007;
const int NN=210;
const int MM=5000010;
/* ****************** */
char ss[MM];
char s1[MM];
const int k_size=2;
const int LEN=100005; //子串最大长度
struct Tire_tree
{
int fail,ge,id;
int t_next[k_size];
int next[k_size];
void init()
{
id=0;
fail=-1;//我普遍习惯用-1表示空
memset(next,-1,sizeof(next));
}
}tire[LEN],tire1[LEN];
int tire_q[LEN]; //用bfs模拟dfs,处理fail指针
void tire_insert(char *str,int l,int root,Tire_tree* tire,int& tire_cnt)
{
int i,x,p=root;
for(i=0;i<l;i++)
{
x = str[i]-'0'; //把字符变成连续的数字
if(tire[p].next[x]==-1)
{
tire_cnt++;
tire[tire_cnt].init();
tire[p].next[x]=tire_cnt;
}
p = tire[p].next[x];
}
tire[p].id = 1;
}
//处理tire中的fail指针
void init_fail(int root,Tire_tree* tire)
{
int tail,head;
int i,fa,p=root;
head=1;
tail=0;
tire[root].fail=root;
tire[root].ge = 0;
for(i=0;i<k_size;i++)
{
if(tire[p].next[i]!=-1)
{
tire_q[++tail] = tire[p].next[i];
tire[ tire_q[tail] ].fail = root;
tire[p].t_next[i] = tire[p].next[i];
}
else
{
tire[p].t_next[i] = root;
// tire[p].next[i]=root;
}
}
while(head<=tail)
{
p = tire_q[head++];
fa = tire[p].fail;
tire[p].ge = tire[p].id + tire[fa].ge;
for(i=0;i<k_size;i++)
{
if(tire[p].next[i]!=-1)
{
tire_q[++tail] = tire[p].next[i];
tire[ tire_q[tail] ].fail = tire[fa].t_next[i];
tire[p].t_next[i] = tire[p].next[i];
}
else
{
// tire[p].next[i] = tire[fa].next[i];
tire[p].t_next[i] = tire[fa].t_next[i];
}
}
}
}
void dfs(int p,int cen,int root,int &tire_cnt)
{
if(tire1[p].id != 0)
{
tire_insert(ss,cen,root,tire,tire_cnt);
}
int i;
for(i = 0; i < k_size; i++)
{
if(tire1[p].next[i] != -1)
{
ss[cen] = i+'0';
dfs(tire1[p].next[i],cen+1,root,tire_cnt);
}
}
}
void merger(int root,int t_root,int &tire_cnt)
{
dfs(t_root,0,root,tire_cnt);
init_fail(root,tire);
}
void solve(char *str,int l,int root,Tire_tree *tire,LL &ans)
{
int i, x, p = root;
for(i = 0; i < l; i++)
{
x = str[i] - '0';
p = tire[p].t_next[x];
if(p==-1)
{
while(1);
}
ans += tire[p].ge;
}
}
void fun_yi(char *str,int l,char *temp,int x)
{
int i,j;
for(i = 0, j = l-x; i < x;i++, j++)
{
temp[j] = str[i];
}
for(i = x, j = 0; i < l; i++, j++)
{
temp[j] = str[i];
}
}
bool tire_find(char* str,int l,int root,Tire_tree* tire)
{
int i, x ,p = root;
for(i = 0; i < l; i++)
{
x = str[i] - '0';
if(tire[p].next[x]==-1)
return false;
p = tire[p].next[x];
}
return (tire[p].id!=0);
}
int main()
{
int cas, ee = 0;
int i, n, l, sum_l;
int limit = sqrt( 100000.0 );
int root1,root2;
int tire_cnt;
int tire_cnt1;
LL ans;
scanf("%d",&cas);
while(cas--)
{
printf("Case #%d:\n",++ee);
ans = 0;
sum_l = 0;
root1 = 0;
root2 = 0;
tire[root1].init();
tire1[root2].init();
tire_cnt = 0;
tire_cnt1 = 0;
init_fail(root1,tire);
init_fail(root2,tire1);
scanf("%d",&n);
for(i = 0; i < n; i++)
{
scanf("%s",ss);
l = strlen(ss);
fun_yi(ss+1,l-1,s1, (int)(ans%(l-1)) );
if(ss[0]=='+')
{
if( !tire_find(s1,l-1,root1,tire) )
{
tire_insert(s1,l-1,root2,tire1,tire_cnt1);
init_fail(root2,tire1);
}
sum_l += l-1;
if(sum_l >= limit)
{
merger(root1,root2,tire_cnt);
sum_l = 0;
tire_cnt1 = 0;
tire1[root2].init();
init_fail(root2,tire1);
}
}
else
{
ans = 0;
solve(s1,l-1,root1,tire,ans);
solve(s1,l-1,root2,tire1,ans);
printf("%I64d\n",ans);
}
}
}
return 0;
}