此题是那次做了GRE的题目后,相同做法的题。
先接着上次说的说说fail树吧:考虑把fail的指针反向建边,由于对于fail节点每个节点所指向的fail唯一,根据fail的原理,我们会从某个节点返回到root,所以fail的反向建边后性质就成了个fail树,这个树表示的是对于如果访问到某个子节点,那么他的祖先节点必然可以作为子串出现,那么鉴于此,我们要统计子串访问多少次,就是该节点返回到root的次数,那么这就是我前面说的dfs序。
题意是:给出n个名字,3种操作,
?string 统计前面是城市居民的作为子串出现在string的次数
+idx 把第idx个人变为城市居民
- idx 把第idx个人变为非城市居民
对于每个?操作,输出次数
现在我们还是那题那样对于每个的总结点标号为idx,然后由于询问有把第idx排除出去的,所有我们要有个映射对应于哪些trie上的节点访问过,可以用bool vis来标记
然后对于dfs序列,我们可以将每个访问过的节点,它所能表示的一段子串+1(即dfs序连续的一段)
对于每个询问,我们只要对应于前面的idx标号,把每个从root一直下去经过的点查询出来就可以了,把其对应的点看有多少点访问过就可以了
if(str[0]=='?')
{
int u=0,ans=0;
for(int i=1;i<len;i++)
{
int c=ac.idx(str[i]);
u=ac.ch[u][c];
ans+=tree.query(1,L[u]);
}
cout<<ans<<endl;
}
对于+-操作其实是可以看做一个同等操作,
我们将前面映射的点所对应的区间L,R全部+1或者-1就代表一个删除操作了
if(str[0]=='+')
{
int num=0;
for(int i=1;i<len;i++)
{
num=num*10+str[i]-'0';
}
if(flag[num])continue;
flag[num]=1;
tree.update(1,L[ac.mp[num]],R[ac.mp[num]],1);
}
-1操作是一样的
然后这里对于dfs序列一段区间的更新操作用线段树就可以了 或者bit也可以
线段树维护的就是区间和,和那个GRE的题目一样
其中ac自动机是模板,dfs序列也很简单对于fail指针反向建边就可以了
for(int i=1;i<=ac.sz;i++)
{addedge(ac.fail[i],i);addedge(i,ac.fail[i]);}