• trie树(二)进阶


    一些相对比较难的题。。。

    First! G

    大意:
    给定一些字符串,要求你确定在改变字符优先级的情况下,某些字符串是否能成为字典序最小的字符串

    思路:

    1.某个字符串能够作为字典序最小的字符串,那么它的首字母一定是最小的。

    2.并且对于所有与它有相同前缀的字符串,该字符串的下一个字母的字典序也一定是比另一个字符串的下一个字母的字典序要小。

    3.另外,如果如果有一个字符串是它的前缀,那么这个前缀一定会比它有更小的字典序,所以这种情况下该字符串就是失败的。

    以上三种情况中,1 3都是很好处理的,我们再仔细看一看情况2

    根据情况二下的规则,每次我们确定两个字母的优先关系时,就给它们连一条有向边,从字典序较小的连向较大的。这样最后我们就会得到一张有向图。判断这个图是否合理很简单,只要没有环就是合理的,因为如果有环的话,两个字母的优先关系就会产生矛盾。所以我们只要多加一个判环处理即可,这里可以用拓扑排序

    code

    1. #include
    2. using namespace std;
    3. #define ll int
    4. #define endl '\n'
    5. const ll N=3e5+10;
    6. ll tr[N][30];
    7. ll idx=0;
    8. ll n,p,ans;
    9. string s[30010];
    10. bool vis[30010];
    11. bool mp[30][30];
    12. bool init[30];
    13. ll du[30];//入度
    14. bool en[N];//字符串结尾
    15. void insert(string s)
    16. {
    17. p=0;
    18. for(int i=0;isize();++i)
    19. {
    20. ll id=s[i]-'a'+1;
    21. if(!tr[p][id]) tr[p][id]=++idx;
    22. p=tr[p][id];
    23. }
    24. en[p]=1;
    25. }
    26. bool find(string ss)
    27. {
    28. ll tag=ss[0]-'a'+1;
    29. for(int i=1;i<=26;++i)
    30. {
    31. if(i==tag) continue;
    32. if(!init[i]) continue;
    33. if(mp[tag][i]) continue;
    34. mp[tag][i]=1;
    35. du[i]++;
    36. }
    37. p=0;
    38. for(int i=0;isize();++i)
    39. {
    40. ll id=ss[i]-'a'+1;
    41. for(int j=1;j<=26;++j)
    42. {
    43. if(id==j) continue;
    44. if(!tr[p][j]) continue;
    45. if(mp[id][j]) continue;
    46. mp[id][j]=1;//i
    47. du[j]++;
    48. }
    49. p=tr[p][id];
    50. if(en[p]&&i!=ss.size()-1) return 0;//如果发现有一个字符串是它的前缀,直接返回非法
    51. }
    52. return 1;
    53. }
    54. bool topu()//拓扑排序判环
    55. {
    56. queue q;
    57. for(int i=1;i<=26;++i) if(du[i]==0) q.push(i);//取入度为0的加入队列
    58. // for(int i=1;i<=26;++i) cout<<(char)('a'+i-1)<
    59. // cout<
    60. while(!q.empty())
    61. {
    62. ll ty=q.front();
    63. q.pop();
    64. for(int i=1;i<=26;++i)
    65. {
    66. if(i==ty) continue;
    67. if(!mp[ty][i]) continue;
    68. mp[ty][i]=0;
    69. du[i]--;
    70. //cout<<"ID="<
    71. if(du[i]==0) q.push(i);
    72. }
    73. }
    74. for(int i=1;i<=26;++i) if(du[i]) return 0;
    75. return 1;
    76. }
    77. int main()
    78. {ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    79. cin>>n;
    80. for(int i=1;i<=n;++i)
    81. {
    82. cin>>s[i];
    83. init[s[i][0]-'a'+1]=1;
    84. insert(s[i]);
    85. }
    86. for(int i=1;i<=n;++i)
    87. {
    88. for(int j=1;j<=26;++j) for(int k=1;k<=26;++k) mp[j][k]=0;
    89. for(int j=1;j<=26;++j) du[j]=0;
    90. if(find(s[i])==0) continue;
    91. if(topu()) vis[i]=1,ans++;
    92. }
    93. cout<
    94. for(int i=1;i<=n;++i) if(vis[i]) cout<
    95. return 0;
    96. }

    病毒检测

    大意:
    给定一个模式串和一系列匹配串,求无法与其匹配的字符串数

    模式中 * 的意思是可以匹配上0个或任意多个字符,而 ? 的意思是匹配上任意一个字母。

    思路:

    思路是trie树上跑dfs

    如果没有两个通配符的话,我们要做字符串匹配,只要建好trie树,按模式串的字符顺序遍历一下trie树就可以了。现在有了两个通配符,我们就有了多个可能性来匹配更多的字符串,所以会考虑到dfs。

    现在讨论一下要如何做dfs

    dfs(a,p)a:模式串的位置 p:trie树的位置

    1.如果在模式串上遍历到的是一个字母(ATCG中的一个),我们只要按普通规则继续往下递归就可以了 dfs(a+1,tr[p][i])

    2.如果遍历到的是一个?,那么就是取ATCG中的任意字符往下递归(只要树上有到它们的路径)

    1. for(int i=1;i<=4;++i)
    2. {
    3. if(tr[p][i]) dfs(a+1,tr[p][i]);
    4. }
    5. return;

    3.如果是遍历到了一个“*”,我们还有两个情况要讨论:
               一:让它作为空串,也就是跳过它,那么a+1,p不变,也就是dfs(a+1,p);

               二:它会去匹配一个字符串,我们可以表示成“ ?+* ”的形式,这样就保证了*可以一直往下匹配,同时我们也可以一个字符一个字符去匹配。

    1. for(int i=1;i<=4;++i)
    2. {
    3. if(!tr[p][i]) continue;
    4. //?+*
    5. dfs(a+1,tr[p][i]);
    6. dfs(a,tr[p][i]);
    7. }

     

    另外,这里六个字符我们可以用一个函数来映射成数字

    code

    1. #include
    2. using namespace std;
    3. #define ll int
    4. #define endl '\n'
    5. const ll N=5e5+10;
    6. ll tr[N][10];
    7. ll cnt[N];
    8. ll idx=0;
    9. ll n;
    10. ll len=0;
    11. ll ans=0;
    12. bitset<500007> vis[1010];
    13. string vir,s;
    14. ll get_id(char x)
    15. {
    16. if(x=='A') return 1;
    17. if(x=='G') return 2;
    18. if(x=='T') return 3;
    19. if(x=='C') return 4;
    20. if(x=='*') return 5;
    21. if(x=='?') return 6;
    22. }
    23. void insert(string s)
    24. {
    25. ll p=0;
    26. for(int i=0;isize();++i)
    27. {
    28. int id=get_id(s[i]);
    29. if(!tr[p][id]) tr[p][id]=++idx;
    30. p=tr[p][id];
    31. }
    32. cnt[p]++;
    33. }
    34. void dfs(ll a,ll p)
    35. {
    36. if(a>=len)
    37. {
    38. ans+=cnt[p];
    39. cnt[p]=0;//清空防止多加
    40. return;
    41. }
    42. if(vis[a][p]) return;
    43. vis[a][p]=1;//标记
    44. int id=get_id(vir[a]);
    45. if(id>=1&&id<=4)
    46. {
    47. if(tr[p][id])
    48. dfs(a+1,tr[p][id]);
    49. return;
    50. }
    51. if(id==6)
    52. {
    53. for(int i=1;i<=4;++i)
    54. {
    55. if(tr[p][i]) dfs(a+1,tr[p][i]);
    56. }
    57. return;
    58. }
    59. if(id==5)
    60. {
    61. dfs(a+1,p);
    62. for(int i=1;i<=4;++i)
    63. {
    64. if(!tr[p][i]) continue;
    65. //?+*
    66. dfs(a+1,tr[p][i]);
    67. dfs(a,tr[p][i]);
    68. }
    69. }
    70. }
    71. int main()
    72. {ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    73. cin>>vir;
    74. len=vir.size();
    75. cin>>n;
    76. for(int i=1;i<=n;++i) cin>>s,insert(s);
    77. //for(int i=1;i<=idx;++i) cout<
    78. dfs(0,0);
    79. cout<
    80. }

    Type Printer 

    大意:
    有一个打印队列,每次可以执行以下操作:

    往末尾添加一个字母/删除一个末尾字母/打印(输出所有字母,但不会删除它们)

    现在要求打印所有字符串(顺序任意),求最小操作数

    ps:打印完最后一个字符串后不要求清空打印队列

    思路:
    不难想到,最长的字符串肯定是最后打印,因为它就不用清空了,就可以省下很多操作次数

    那么我们的打印规则就是:尽可能利用好每一个前缀,也就是把每一个前缀的对应的所有字符串都打印完再打印别的字符串,同时要最后打印那个最长的字符串

    那么自然就还是会想到dfs

    最后打印最长的字符串,可以在建trie树时给这个字符串经过的链打上标记

    至于普通的打印过程,就是深度优先遍历以下就好了

    具体看代码吧

    有一个点要注意,就是当你发现当前已经到达某一个字符串的结尾时,ans++,但是不能直接return,因为有可能其它字符串的起点刚好就是这个字符串的结尾,所以这时我们应该继续dfs下去,而不是返回。这一点根其它题目不太一样,也让我wa了好多发。。。

    1. #include
    2. using namespace std;
    3. #define ll int
    4. const ll N=25010;
    5. ll tr[N*20][30];
    6. ll idx=0;
    7. ll n;
    8. string s[N];
    9. ll ma=0;
    10. ll tag=0;
    11. bool ne[N*20];//对应最长序列,要最后访问
    12. string ans="";
    13. bool en[N*20];//单词结尾的地方
    14. char mp[N*20];
    15. ll sd=0;
    16. void insert(ll k)
    17. {
    18. ll p=0;
    19. string ss=s[k];
    20. for(int i=0;isize();++i)
    21. {
    22. ll id=ss[i]-'a'+1;
    23. if(!tr[p][id]) tr[p][id]=++idx;
    24. p=tr[p][id];
    25. //num[p]++;//有多少单词经过这里
    26. mp[p]=ss[i];//反向映射
    27. if(k==tag) ne[p]=1;//长度最长的单词经过这里
    28. }
    29. en[p]=1;//结尾标记
    30. }
    31. void dfs(ll p)
    32. {
    33. if(en[p])
    34. {
    35. ans+='P';
    36. sd++;
    37. //这里不能直接返回。。。
    38. }
    39. if(sd==n)
    40. {
    41. cout<size()<
    42. for(int i=0;isize();++i) cout<
    43. return;
    44. }
    45. for(int i=1;i<=26;++i)
    46. {
    47. if(ne[tr[p][i]]) continue;//先跳过最长的单词
    48. if(!tr[p][i]) continue;
    49. ans+=mp[tr[p][i]];
    50. dfs(tr[p][i]);
    51. ans+='-';//该前缀后面的东西已经打印好了,就可以把这个字符删掉了
    52. }
    53. for(int i=1;i<=26;++i)//现在来处理最长的字符串
    54. {
    55. if(!tr[p][i]) continue;
    56. if(!ne[tr[p][i]]) continue;
    57. ans+=mp[tr[p][i]];
    58. dfs(tr[p][i]);
    59. ans+='-';
    60. //break;
    61. }
    62. }
    63. int main()
    64. {ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    65. cin>>n;
    66. for(int i=1;i<=n;++i)
    67. {
    68. cin>>s[i];
    69. ll len=s[i].size();
    70. if(len>ma)
    71. {
    72. ma=len;
    73. tag=i;/标记最长
    74. }
    75. }
    76. for(int i=1;i<=n;++i)
    77. {
    78. insert(i);
    79. }
    80. dfs(0);
    81. return 0;
    82. }

     未完待续...

     

  • 相关阅读:
    运动装备有哪些?2022年值得买的运动装备分享
    2022年前端还好找工作吗?
    【老生谈算法】matlab实现数字图像复原算法源码——数字图像复原算法
    Android Qcom Sensor架构学习
    电商行业少不了的营销方式——邮件营销
    C++ Reference: Standard C++ Library reference: C Library: cwchar: vfwprintf
    【java期末复习题】第5章 继承与多态
    echarts 解决tooltip显示框随鼠标移动,且显示不全问题_付月半子的博客-CSDN博客
    OpenResty中如何实现,按QPS、时间范围、来源IP进行限流
    【vue】main.js中全局挂载方法、组件:
  • 原文地址:https://blog.csdn.net/sophilex/article/details/126315165