题目概述
题目大意
要求你写出一个网络市政系统,用于评定每篇文章的“政治程度”。每篇文章的“政治程度”的含义就是文章中出现了多少个参议员的名字。这里要注意,参议员的名字可能是交叉的。 这个系统还需支持三种操作:
- 将某个参议员的名字从名单中抹去(不再考虑此议员)
- 将某个已被删除的议员的名字重新加入名单。
- 输出给定的文章的“政治程度”。
输入大意
每个测试点只包括一组数据。数据的第一行为n,k,分别表示共有n个操作,参议员名单中共有k个名字。接下来的k行,每一行包括一个参议员的名字。接下来的n行,每一行代表一个操作,格式分别为:
- 以‘?’开头的操作为询问操作,表示?后的字符串(文章)的政治程度的大小;
- 以‘+’开头的操作为添加操作,表示将编号为的参议员的名字重新加入名单;
- 以‘-’开头的操作为删除操作,表示将编号为的参议员的从名单中删除。
输出大意
对于每个‘?’操作,输出给定文章的政治程度。
标准样例
Input: |
---|
7 3 a aa ab ?aaab -2 ?aaab -3 ?aaab +2 ?aabbaa |
Output: |
6 4 3 6 |
务必注意
这里面要处理的细节问题还是比较少的,就是要保证每个参议员的姓名不能在名单中被重复删除,在进行删除操作的修改之前一定要判断是否已经被删除过,添加操作也一样,不能重复地添加。这一点在样例中就可以看出来。
心路历程
Day1:
这天中午拿到这道题,回到机房马上就看了一样,首先要说的就是题面比较好翻译,不过还是一定程度上忽视了一些细节上的考虑...
时隔一个多月,总是一味地狂刷模板题的我已经大概忘却了我的原则吧,于是读懂题意上来就写了一波AC自动机,修改的操作直接对表示该单词的端点进行修改。但是样例就挂掉了,看了很久样例,画了一个样例的Trie树(挺小的),一下就发现了问题:虽然删除操作的时候去除了该点的标记,但是在AC自动机在进行Match操作的时候还是会走到已经没有标记的节点,“浪费”了一些字母。
于是我开始转变思路,额……day1结束了也没想出来这道题怎么做。这道题就这么被push到我日程的栈顶了。
Day2:
早上再来分析这道题的时候,已经开始想要改变思路了,但是对于int(1e6)的数据范围的AC自动机中的串的添加删除操作,还是想不出什么好点子。
但是在上午,当我正在YY在Trie树上怎么跳才能让我的串被充分匹配而且不“浪费”字母的时候我又脑补到了一个做法:就是每一个点都记录一下走到这个点可以产生出多少名字(子串),所以说每删除或者添加一个名字的时候就会对Trie树上它的子节点上的值造成一定的影响。
事实证明,它已经比较接近正解了,但是我们还有一个事儿要考虑啊,就是当我们跳fail的时候“收集”到的那些标记,也有可能发生修改啊。换句话说就是,每当我们修改一个串的时候,不仅是它子树上的节点的统计值(暂且就起了个名)会被它的改变所影响,那些跳fail能经过它的点的统计值也有可能被影响啊!
(请原谅笔者刚刚的词穷,在这里解释一下统计值的含义:就是在Tire树上走到这个点一共可以收集到多少的标记,也就是说某个节点所表示的字符串包括多少名单上的名字)
既然跳fail会影响统计值,不妨建成fail树再搞搞?
Day3:
今天早上就有种隐隐约约觉得今天可以过掉这道题的预感(虽然是口毒奶)。
建出了fail树,接下来的就是分析fail树的性质了。对于修改来说,每次修改其实就是一次子树修改。对于查询来说,每次查询就是将要分析的文章在Trie树上Match一遍,然后统计fail树意义下的统计值。
那么这道题就已经很明朗了,接下来就是一语道破的环节了。对于子树的修改和统计值的查询,其实只要维护出fail树的dfs出栈入栈序就可以了。每次修改以k为根的子树的时候只需就该从k入栈到k出栈的这一区间就可以了。
题目正解
以上内容仅供笔者回忆和读者嘲笑,接下来才是正题。
首先建出一个AC自动机,构造fail指针,顺便处理出来每一个名字编号在AC自动机中对应的节点的编号。接下来建出fail树并dfs处理出每个AC自动机中的节点的出栈入栈时间戳,也就是在dfs序列中第一次出现和第二次出现的位置。
那么序列上某个节点的入栈点和出栈点之间的点就是该节点子树(fail树意义下)上的节点,也就是说修改此节点会造成影响的点。
于是问题一下就转变为了fail树dfs序的区间修改和前缀和查询,柳暗花明啊!
实现代码
实现
我选用后缀数组来搞这道题的序列维护。有人跟我讲用后缀数组不差分没法区间修改啊?不过只有前缀和查询的话,就没有关系了啊,在首位置添加影响,在末位置+1消除影响就可以了。
代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
#define lowbit(k) ((k)&(-(k)))
int get_num(char *s) {
int len=strlen(s);
int k=0,pos=0;
while(pos<len && s[pos]>='0' && s[pos]<='9')
k=k*10+(s[pos++]-'0');
return k;
}
const int maxn=int(1e6)+10;
int n,m;
char S[maxn];
int pos[maxn];
int in[maxn];
int out[maxn];
int drop[maxn];
int dfs_clock=0;
struct Aho {
int siz;
int ch[maxn][26],fail[maxn],cnt[maxn];
std :: queue <int> que;
void Init();
void Insert(char*,int);
void Build();
void dfs(int,int);
}aho;
void Aho :: Init() {
siz=1;
memset(fail,0,sizeof fail);
memset(cnt,0,sizeof cnt);
for(int i=0;i<maxn;i++) memset(ch[i],0,sizeof ch[i]);
while(que.size()) que.pop();
return;
}
void Aho :: Insert(char *s,int id) {
int len=strlen(s);
int now=0;
for(int i=0;i<len;i++) {
if(!ch[now][s[i]-'a'])
ch[now][s[i]-'a']=siz++;
now=ch[now][s[i]-'a'];
}
pos[id]=now;
cnt[now]++;
return;
}
void Aho :: Build() {
que.push(0);
while(que.size()) {
int u=que.front(); que.pop();
for(int i=0;i<26;i++) if(ch[u][i]) {
int v=ch[u][i];
que.push(v);
if(!u) fail[v]=0;
else fail[v]=ch[fail[u]][i];
} else ch[u][i]=ch[fail[u]][i];
}
}
struct Edge {
int from,to;
int next;
}eage[maxn];
int head[maxn];
int tot=-1;
void Edge_add(int x,int y) {
eage[++tot].from=x;
eage[tot].to=y;
eage[tot].next=head[x];
head[x]=tot;
}
void Dfs(int k,int fa) {
in[k]=++dfs_clock;
for(int i=head[k];~i;i=eage[i].next) if(eage[i].to!=fa)
Dfs(eage[i].to,k);
out[k]=++dfs_clock;
return;
}
long long sum[maxn*2];
void BIT_modify(int k,int val) {
for(int i=k;i<=aho.siz*2;i+=lowbit(i))
sum[i]+=1LL*val;
return;
}
long long BIT_query(int k) {
long long res=0;
for(int i=k;i>=1;i-=lowbit(i))
res+=sum[i];
return res;
}
int Str_query(char *s) {
int now=0,ans=0;
int len=strlen(s);
for(int i=0;i<len;i++) {
while(!aho.ch[now][s[i]-'a'] && now)
now=aho.fail[now];
now=aho.ch[now][s[i]-'a'];
ans+=BIT_query(in[now]);
}
return ans;
}
int main() {
freopen("input.txt","r",stdin);
freopen("output.txt","w",stdout);
memset(head,-1,sizeof head);
scanf("%d%d",&m,&n);
aho.Init();
for(int i=1;i<=n;i++) {
scanf("%s",S);
aho.Insert(S,i);
}
aho.Build();
for(int i=1;i<aho.siz;i++) Edge_add(aho.fail[i],i);
Dfs(0,-1);
for(int i=1;i<=n;i++)
drop[i]=1,
BIT_modify(in[pos[i]],1),
BIT_modify(out[pos[i]]+1,-1);
for(int i=1;i<=m;i++) {
scanf("%s",S);
if(S[0]=='?') {
cout<<Str_query(S+1)<<endl;
}
else {
int id=get_num(S+1);
int dir=(S[0]=='-')?-1:1;
if(drop[id]==dir) continue;
BIT_modify(in[pos[id]],dir);
BIT_modify(out[pos[id]]+1,0-dir);
drop[id]=0-drop[id];
}
}
return 0;
}