Codeforces ProblemSet 163E 解题报告

题目概述

题目大意

要求你写出一个网络市政系统,用于评定每篇文章的“政治程度”。每篇文章的“政治程度”的含义就是文章中出现了多少个参议员的名字。这里要注意,参议员的名字可能是交叉的。 这个系统还需支持三种操作:

  • 将某个参议员的名字从名单中抹去(不再考虑此议员)
  • 将某个已被删除的议员的名字重新加入名单。
  • 输出给定的文章的“政治程度”。

传送门


输入大意

每个测试点只包括一组数据。数据的第一行为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;
}

转载于:https://www.cnblogs.com/BulaBulaCHN/p/6230022.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值