题解 P2292 【[HNOI2004]L语言】

题目链接

Solution [HNOI2004]L语言

题目大意:给定一个有\(n\)个单词的字典,以及\(m\)段文章,询问每段文章可以被字典理解的最长前缀

分析:这题主要难在\(dp\)的优化

我们用\(f[i]\)表示前\(i\)个字符串是否被理解,显然:

$f[i] = f[i];||;f[j-1] \quad j \leq i ;and;str[j,i] \in words $

\(ans = max\{i \mid f[i] = 1\}\)

\(str\)表原字符串,\(words\)就是字典(蒟蒻实在不知道如何表达这个\(dp\)方程QAQ)

方程显而易见,关键我们如何做\(dp\)

这题\(n\)很小,每个单词长度不超过\(10\),每段文章长度不超过\(1M\),即\(10^6\)

如果暴力\(O(N^2)dp\)复杂度直接上天,关键在于每个单词长度不超过\(10\),这样我们就可以限定枚举\(j\)的范围

如何判断一个子串在不在字典里?

  • \(hash\)
  • \(STL\)大法吼啊(unordered_set)

感觉能过,自己没写

因为我们枚举\(j\)的顺序是没有关系的,所以我们可以倒着枚举\(j\)

然后把单词都倒着插入一颗\(Trie\)树,然后做\(dp\)枚举\(j\)时就在\(Trie\)树走,走到\(Trie\)树上的有标记的节点,那么此时\(str[j,i] \in words\)

说起来很抽象,代码:

#include <cstring>
#include <cstdio>
using namespace std;
const int maxn = 1e6 + 100;
char str[maxn];
namespace Trie{
    const int maxnode = 512,sigmasize = 26;
    int ch[maxnode][sigmasize],val[maxnode],tot;
    inline int idx(char c){return c - 'a';}
    inline void insert(){
        int len = strlen(str + 1),u = 0;
        for(int i = len;i >= 1;i--){//倒着插入
            int c = idx(str[i]);
            if(!ch[u][c])ch[u][c] = ++tot;
            u = ch[u][c];
        }
        val[u] = 1;
    }
    bool f[maxn];
    inline void query(){
        int len = strlen(str + 1),ans = 0;
        memset(f,0,sizeof(f));f[0] = 1;//初始化
        for(int i = 1;i <= len;i++){
            int u = 0;
            for(int j = i;j >= 1;j--){//倒着枚举j
                int c = idx(str[j]);
                if(!ch[u][c])break;//走不动了后面也不可能有单词了
                u = ch[u][c];
                if(val[u])f[i] |= f[j - 1];//str[j,i]在字典里
            }
            if(f[i])ans = i;//刷新ans
        }
        printf("%d\n",ans);
    }
}
int n,m;
int main(){
#ifdef LOCAL
    freopen("fafa.in","r",stdin);
#endif
    scanf("%d %d",&n,&m);
    for(int i = 1;i <= n;i++)
        scanf("%s",str + 1),Trie::insert();
    for(int i = 1;i <= m;i++)
        scanf("%s",str + 1),Trie::query();
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/colazcy/p/11515094.html