zoukankan      html  css  js  c++  java
  • 【Luogu】P2292 [HNOI2004]L语言 题解

    前置芝士:(Trie)字典树

    这道题,说是AC自动机,实际上一个(Trie+)队列轻松搞定。

    首先,我们对所有单词建一棵(Trie)

    然后,定义一个空队列(Q),初始时把(-1)放进去(因为字符串下标从(0)开始,待会儿详细叙述原因)。

    接着,对于每一篇询问的文章(T),进行如下操作:

    1. 取出队头元素,假设是(x)
    2. 更新(ans=max(ans,x))
    3. (T[x+1])开始枚举,进行(Trie)上的匹配。(这也解释了为什么刚开始要把(-1)放进去,因为这样才能从(0)开始枚举)
    4. 如果成功匹配(T[i]),继续枚举,直到第(5)步被执行或者(ige T.length)
    5. 否则,如果发现匹配不了了,立即退出循环,跳回第(1)步。
    6. 如果成功匹配(T[i]),并且发现这里有字符串结尾标记,则说明成功匹配了一个单词,把(i)放进队尾。(注意:此时不能立即退出,待会儿讲原因)
    7. 执行(1-6)步,直到队列为空。

    说明一下第(6)步,此时为什么不能直接退出呢?

    比如:词典为({what,whatis}),文章为(whatisbalabala)

    如果直接退出,则匹配到(i=3)时就退出了,最后输出答案为(4)。(而实际为(6)

    这样,就可以开心地(code)啦:(看完代码不要心急,继续往下看)

    #include <bits/stdc++.h>
    using namespace std;
    int n,m,trie[1000005][26],tot,c[1000005];
    char tmp[1000005];
    queue<int> q;
    inline void addstring(char a[]){//添加字符串
        int len=strlen(a),pos=0;
        for(int i=0;i<len;i++){
            if(!trie[pos][a[i]-'a']){
                trie[pos][a[i]-'a']=++tot;
                pos=trie[pos][a[i]-'a'];
            }
            else pos=trie[pos][a[i]-'a'];
        }
        c[pos]=true;
    }
    inline int find(char a[]){
        memset(flag,0,sizeof(flag));
        int len=strlen(a),pos=0,ans=-1;q.push(-1);
        while(!q.empty()){
            int x=q.front();q.pop();//步骤1
            ans=max(ans,x);pos=0;//步骤2
            for(int i=x+1;i<len;i++){//步骤3
                if(trie[pos][a[i]-'a']) pos=trie[pos][a[i]-'a'];//步骤4
                else break;//步骤5
                if(c[pos]) q.push(i);//步骤6
            }
        }
        return ans==-1?0:ans+1;//字符串下标以0开始,而题目中以1开始
    }
    int main(){
        scanf("%d%d",&n,&m);
        for(register int i=1;i<=n;i++){
            scanf("%s",tmp);addstring(tmp);
        }
        for(register int i=1;i<=m;i++){
            scanf("%s",tmp);printf("%d
    ",find(tmp));
        }
        return 0;
    }
    

    开心的交上去,咦?怎么只有(73pts)

    经不懈思考,终于构造出能卡掉的数据:

    字典:({a,aa,aaa,...,aaaaaaaaaa})

    文章:(underbrace {aaa...aaa}_{10^6个a})

    于是,对于几乎每个位置(x),都被插入队列至少(10)次,速度也就呵呵了......

    那么,如何防止一个位置被重复插入?很简单,做个标记就行了。

    改进后的代码:((AC))

    #include <bits/stdc++.h>
    using namespace std;
    int n,m,trie[1000005][26],tot,c[1000005],flag[1000005];//flag即为标记数组
    char tmp[1000005];
    queue<int> q;
    inline void addstring(char a[]){
        int len=strlen(a),pos=0;
        for(int i=0;i<len;i++){
            if(!trie[pos][a[i]-'a']){
                trie[pos][a[i]-'a']=++tot;
                pos=trie[pos][a[i]-'a'];
            }
            else pos=trie[pos][a[i]-'a'];
        }
        c[pos]=true;
    }
    inline int find(char a[]){
        memset(flag,0,sizeof(flag));//初始化标记数组
        int len=strlen(a),pos=0,ans=-1;q.push(-1);
        while(!q.empty()){
            int x=q.front();q.pop();
            ans=max(ans,x);pos=0;
            if(flag[x]) continue;//判断一下该位置是否已经有标记了,如果有就continue
            if(x!=-1) flag[x]=1;//否则做个标记
            for(int i=x+1;i<len;i++){
                if(trie[pos][a[i]-'a']) pos=trie[pos][a[i]-'a'];
                else break;
                if(c[pos]) q.push(i);
            }
        }
        return ans==-1?0:ans+1;
    }
    int main(){
        scanf("%d%d",&n,&m);
        for(register int i=1;i<=n;i++){
            scanf("%s",tmp);addstring(tmp);
        }
        for(register int i=1;i<=m;i++){
            scanf("%s",tmp);printf("%d
    ",find(tmp));
        }
        return 0;
    }//开心的结束
    

    最后,蒟蒻写博客不易,恳请大佬点个赞!

  • 相关阅读:
    如何处理消息堆积
    如何避免消息的重复发送
    内存泄漏和内存溢出的关系
    数据挖掘
    servlet
    数据驱动安全需三大核心新技术
    JS 入门经典 第三章 判断、循环和函数
    JS 高级程序设计 第三章
    JS入门经典
    JS高级程序设计1-2章
  • 原文地址:https://www.cnblogs.com/acceptedzhs/p/12262015.html
Copyright © 2011-2022 走看看