P2292 [HNOI2004]L语言

题面

题目描述

标点符号的出现晚于文字的出现,所以以前的语言都是没有标点的。现在你要处理的就是一段没有标点的文章。

一段文章 \(T\) 是由若干小写字母构成。一个单词 \(W\) 也是由若干小写字母构成。一个字典 \(D\) 是若干个单词的集合。我们称一段文章 \(T\) 在某个字典 \(D\) 下是可以被理解的,是指如果文章 \(T\) 可以被分成若干部分,且每一个部分都是字典 \(D\) 中的单词。

例如字典 \(D\) 中包括单词 \(\{‘is’, ‘name’, ‘what’, ‘your’\}\) ,则文章 \(‘whatisyourname’\) 是在字典 \(D\) 下可以被理解的,因为它可以分成 \(4\) 个单词: \(‘what’, ‘is’, ‘your’, ‘name’\) ,且每个单词都属于字典 \(D\) ,而文章 \(‘whatisyouname’\) 在字典 \(D\) 下不能被理解,但可以在字典 $D’=D+ { ‘you’ } $ 下被理解。这段文章的一个前缀 \(‘whatis’\) ,也可以在字典 \(D\) 下被理解,而且是在字典 \(D\) 下能够被理解的最长的前缀。

给定一个字典 \(D\) ,你的程序需要判断若干段文章在字典 \(D\) 下是否能够被理解。并给出其在字典 \(D\) 下能够被理解的最长前缀的位置。

输入输出格式

输入格式:

输入文件第一行是两个正整数 \(n\) 和 \(m\) ,表示字典 \(D\) 中有 \(n\) 个单词,且有 \(m\) 段文章需要被处理。之后的 \(n\) 行每行描述一个单词,再之后的 \(m\) 行每行描述一段文章。

其中 \(1 \leq n, m \leq 20\) ,每个单词长度不超过 \(10\) ,每段文章长度不超过 \(1M\) 。

输出格式:

对于输入的每一段文章,你需要输出这段文章在字典 \(D\) 可以被理解的最长前缀的位置。

输入输出样例

输入样例:

  1. 4 3
  2. is
  3. name
  4. what
  5. your
  6. whatisyourname
  7. whatisyouname
  8. whaisyourname

输出样例:

  1. 14
  2. 6
  3. 0

思路

今天的任务是复习 \(Trie\) 树和 \(AC\) 自动机! --Uranus

\(30 \ mins \ later\)

艹 ---Uranus

今天复习字符串算法,然后就随到了这道字符串毒瘤题。调试了很久,终于过了。

首先拿到这题,显然要先建立一棵 \(Trie\) 或者造一台 \(AC\) 自动机 (量词和动词都没有错呢) ,然而我太蒻了忘了怎么跳 \(AC\) 自动机的 \(fail\) 指针,于是就写的 \(Trie\) 。

接下来如何去匹配呢?我首先想到的是深度优先搜索。对于一段文章,每次查询到到一个前缀存在于 \(Trie\) 中,就选择从根节点重新来找单词,还是继续找下去。这样就可以很容易写出深搜代码:

  1. int dfs(int now)//now表示当前查找的开始位置
  2. {
  3. int p=0,re=0;//re为返回值,p为Trie的节点编号,root=0
  4. for(register int i=now;i<tot_len;i++)//tot_len为文章的长度
  5. {
  6. int id=str[i]-'a';
  7. if(!nex[p][id]) return re;//不存在这个单词
  8. p=nex[p][id];
  9. if(len[p]) re=max(re,len[p]+dfs(i+1));//len!=0时表示当前单词的长度(Trie中该节点深度),考虑重新搜索
  10. }
  11. return re;//溜了溜了
  12. }

然后就有了 \(74\) 分:

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. const int MAXN=210;
  4. int n,m,cnt,tot_len,nex[MAXN][26],len[MAXN];
  5. string str;
  6. inline void add()
  7. {
  8. int p=0;
  9. for(register int i=0;i<tot_len;i++)
  10. {
  11. int id=str[i]-'a';
  12. if(!nex[p][id]) nex[p][id]=++cnt;
  13. p=nex[p][id];
  14. }
  15. len[p]=tot_len;
  16. }
  17. int dfs(int now)
  18. {
  19. int p=0,re=0;
  20. for(register int i=now;i<tot_len;i++)
  21. {
  22. int id=str[i]-'a';
  23. if(!nex[p][id]) return re;
  24. p=nex[p][id];
  25. if(len[p]) re=max(re,len[p]+dfs(i+1));
  26. }
  27. return re;
  28. }
  29. int main()
  30. {
  31. cin>>n>>m;
  32. while(n--)
  33. {
  34. cin>>str;
  35. tot_len=str.length();
  36. add();
  37. }
  38. while(m--)
  39. {
  40. cin>>str;
  41. tot_len=str.length();
  42. cout<<dfs(0)<<endl;
  43. }
  44. return 0;
  45. }

在一番玄学优化无用之后开始思考使用 \(dp\) 来优化搜索。首先写一个 \(bool\) 函数 \(fd(int \ l,int \ r)\) 表示文章的 \([l,r]\) 区间所形成的单词是否存在于 \(Trie\) 中,然后定义 \(bool\) 变量 \(f[i]\) 表示 文章中的 \([1,i]\) 区间是否可以为最长前缀。那么有

\[f[i]= \{ k \ | \ (f[k]=true \ or \ k=-1) \ and \ fd(k+1,i)=true \}
\]

于是我们就有了 \(24\) 分代码:

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. const int MAXN=210;
  4. int n,m,cnt,len,ans,nex[MAXN][26];
  5. bool en[MAXN],f[1100000];
  6. string str;
  7. inline void add()
  8. {
  9. int p=0;
  10. for(register int i=0;i<len;i++)
  11. {
  12. int id=str[i]-'a';
  13. if(!nex[p][id]) nex[p][id]=++cnt;
  14. p=nex[p][id];
  15. }
  16. en[p]=true;
  17. }
  18. inline bool fd(int l,int r)
  19. {
  20. int p=0;
  21. for(register int i=l;i<=r;i++)
  22. {
  23. int id=str[i]-'a';
  24. if(!nex[p][id]) return false;
  25. p=nex[p][id];
  26. }
  27. return en[p];
  28. }
  29. int main()
  30. {
  31. ios::sync_with_stdio(false);
  32. cin.tie();
  33. cout.tie();
  34. cin>>n>>m;
  35. while(n--)
  36. {
  37. cin>>str;
  38. len=str.length();
  39. add();
  40. }
  41. while(m--)
  42. {
  43. cin>>str;
  44. len=str.length(),ans=0;
  45. for(register int i=0;i<len;i++)
  46. {
  47. f[i]=false;
  48. for(register int j=max(i-len,-1);j<i;j++)
  49. if((j==-1||f[j])&&fd(j+1,i))
  50. {f[i]=true;ans=i+1;break;}
  51. }
  52. cout<<ans<<endl;
  53. }
  54. return 0;
  55. }

\(24\) 分?没错,改用了动态规划之后时间反而变慢了,这是因为填表法的时间复杂度太高了。改用刷表法,利用前面的状态,就可以大大降低时间复杂度。

AC代码:

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. const int MAXN=210;
  4. int n,m,cnt,len,ans,nex[MAXN][26];
  5. bool en[MAXN],f[1100000];
  6. string str;
  7. inline void add()
  8. {
  9. int p=0;
  10. for(register int i=0;i<len;i++)
  11. {
  12. int id=str[i]-'a';
  13. if(!nex[p][id]) nex[p][id]=++cnt;
  14. p=nex[p][id];
  15. }
  16. en[p]=true;
  17. }
  18. int main()
  19. {
  20. ios::sync_with_stdio(false);
  21. cin.tie();
  22. cout.tie();
  23. cin>>n>>m;
  24. while(n--)
  25. {
  26. cin>>str;
  27. len=str.length();
  28. add();
  29. }
  30. while(m--)
  31. {
  32. cin>>str;
  33. memset(f,0,sizeof f);
  34. len=str.length(),ans=0;
  35. for(register int i=-1;i<len;i++)
  36. if(i==-1||f[i])
  37. {
  38. int j=i+1,id=str[j]-'a',p=nex[0][id];
  39. while(p&&j<len)
  40. {
  41. if(en[p]) f[j]=true;
  42. id=str[++j]-'a',p=nex[p][id];
  43. }
  44. }
  45. for(register int i=len-1;i>=0;i--) if(f[i]) {ans=i+1;break;}
  46. cout<<ans<<endl;
  47. }
  48. return 0;
  49. }

总结

用了三种算法,过了一种。那要是在 \(NOIP\) 赛场上,写挂了之后又不知道自己挂了该怎么办呢?所以分析时间复杂度就很重要了。

插入操作的时间比较少,我们就不考虑在时间复杂度之中了。首先我们定义一个单词的长度为 \(|P|\) ,一段文章的长度为 \(|S|\) 。

第一种解法:

最坏情况下,每次 \(dfs\) 都考虑是否重新 \(dfs\) ,那么查询复杂度为 \(O(|S|*|P|!)\) ,总的时间复杂度就是 \(O(m|S|*|P|!)\) 。

第二种解法:

一次 \(fd\) 函数的调用的时间复杂度为 \(O(|S|)\) ,动态规划的两层循环是 \(O(|S|^2)\) 的,所以总的时间复杂度就是 \(O(m|S|^3)\) 。这样写的话时间复杂度就与 \(|P|\) 无关而与 \(|S|\) 关系太大了,而 \(|S|>>|P|\) ,自然得分很低。

第三种解法:

一次刷表的时间为 \(|P|\) , 总共循环 \(|S|\) 次,所以总的时间复杂度为 \(O(m|S||P|)\) 了,显然能过。

Luogu P2292 [HNOI2004]L语言(Trie+dp)的更多相关文章

  1. 洛谷:P2292 [HNOI2004]L语言(DP+Trie树)

    P2292 [HNOI2004]L语言 题目链接:https://www.luogu.org/problemnew/show/P2292 题目描述 标点符号的出现晚于文字的出现,所以以前的语言都是没有 ...

  2. 洛谷.2292.[HNOI2004]L语言(Trie DP)

    题目链接 /* 简单的DP,查找是否有字典中的单词时在Trie树上做 要注意在最初Match(0)一遍后,i还是要从0开始匹配,因为如果有长度为1的单词,Match(i+1)不会从1更新 1M=102 ...

  3. Luogu P2292 [HNOI2004]L语言

    题目链接 \(Click\) \(Here\) 好久没写\(DP\)了真是水平下降不少,一眼把这个题搞成贪心了,然后一发交上只有\(37\)分\(QwQ\) 这个题好像还可以\(AC\)自动机胡搞?不 ...

  4. 2021.11.09 P2292 [HNOI2004]L语言(trie树+AC自动机)

    2021.11.09 P2292 [HNOI2004]L语言(trie树+AC自动机) https://www.luogu.com.cn/problem/P2292 题意: 标点符号的出现晚于文字的出 ...

  5. 洛谷 P2292 [HNOI2004] L语言 解题报告

    P2292 [HNOI2004] L语言 题目描述 标点符号的出现晚于文字的出现,所以以前的语言都是没有标点的.现在你要处理的就是一段没有标点的文章. 一段文章\(T\)是由若干小写字母构成.一个单词 ...

  6. 【BZOJ1212】[HNOI2004]L语言 Trie树

    [BZOJ1212][HNOI2004]L语言 Description 标点符号的出现晚于文字的出现,所以以前的语言都是没有标点的.现在你要处理的就是一段没有标点的文章. 一段文章T是由若干小写字母构 ...

  7. 洛谷(cogs 1293/bzoj 1212) P2292 [HNOI2004]L语言

    1293. [HNOI2004] L语言 ★★★   输入文件:language.in   输出文件:language.out   简单对比时间限制:1 s   内存限制:162 MB [题目描述] ...

  8. BZOJ1212[HNOI2004]L语言——trie树+DP

    题目描述 标点符号的出现晚于文字的出现,所以以前的语言都是没有标点的.现在你要处理的就是一段没有标点的文章. 一段文章T是由若干小写字母构成.一个单词W也是由若干小写字母构成.一个字典D是若干个单词的 ...

  9. [HNOI2004][bzoj1212] L语言 [Trie+dp]

    题面 传送门 思路 无后效性 显然,不管某个前缀的理解方式是怎么样的,如果它能被理解,那么前面的决策对于后面的决策而言都是等价的 因此这题可以DP DP方程 令$dp[i]$表示前缀i是否能被理解 那 ...

随机推荐

  1. Xcode9.4.1官方下载链接地址

    More Downloads for Apple Developershttps://developer.apple.com/download/more/ Xcode 9.4.1https://dow ...

  2. mac brew 安装 php 环境

    548  brew search php 549  brew tap homebrew/dupes 550  brew tap josegonzalez/homebrew-php 551  brew ...

  3. 一个tcp连接可以发多少http请求

    -----来自:松若章 -----zhuanlan.zhihu.com/p/61423830 曾经有这么一道经典面试题:从 URL 在浏览器被被输入到页面展现的过程中发生了什么?相信大多数准备过的同学 ...

  4. Leetcode166. Fraction to Recurring Decimal分数到小数

    给定两个整数,分别表示分数的分子 numerator 和分母 denominator,以字符串形式返回小数. 如果小数部分为循环小数,则将循环的部分括在括号内. 示例 1: 输入: numerator ...

  5. 1.spark核心RDD特点

    RDD(Resilient Distributed Dataset) Spark源码:https://github.com/apache/spark   abstract class RDD[T: C ...

  6. 通过真值树解析布尔表达式(eg:A&B|C)

    第一步:求出一个表达式的truth tree 1.生成真值表 2.根据真值表生成真值树(合并短路产生相同的两个子树) /**************************************** ...

  7. leetcode 131 Palindrome Pairs

    lc131 Palindrome Pairs 解法1: 递归 观察题目,要求,将原字符串拆成若干子串,且这些子串本身都为Palindrome 那么挑选cut的位置就很有意思,后一次cut可以建立在前一 ...

  8. 二分图——poj2446匈牙利算法

    /* 怎么建图: 首先分集合:不能相连的点必然在一个集合里,即对角点 再确定怎么连边: 一个点可以向上下左右连边,如果遇到了洞则不行 dfs(i),让匹配到的点接受i作为match结果 寻找增广路时, ...

  9. python3-常用模块之openpyxl(1)

    1.创建工作簿 from openpyxl import Workbook # 创建excel对象 wb = Workbook() # 获取第一个sheet = wb.active # 单元格写入内容 ...

  10. <爬虫>用正则爬取B站首页图片

    import re import requests headers = { 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Apple ...