zoukankan      html  css  js  c++  java
  • 【SAM】BZOJ3998-弦论

    【题目大意】

    给出一个字符串,求第k大的子串。(输入1表示子串可重复,0表示不可重复)

    【思路】

    显然,k大子串是后缀自动机的经典题型,可以利用后缀自动机的性质来解决。对于字符串

    [前铺1]"abcbc",我们可以画出它的后缀自动机,如下图:

    Pre树类似于AC自动机中的fail树,即将pre方向形成一棵树。对于上图,它的pre树如下:

     

    [前铺2]考虑字符串s的任意非空子串t。我们称终点集合right(t)为:s中所有是t出现位置终点的集合。例如:对于字符串ATCGTCGT来说,所有的CG的末尾位置的集合是{4,7},也就是说right(“CG”)={4,7}。如果两个子串t_1t_2终点集合一致,即right(t_1)=right(t_2),那么称它们为“终点等价”。因此,所有s的非空子串可以根据终点等价性分成若干类。

    例如:对于字符串abcbc来说,它的所有子串构成的集合可以按照上述等价关系进行如下划分:{{a},{b},{c,bc},{ab},{cb,bcb,abcb},{abc},{cbc,bcbc,abcbc}}

     【知识点】pre树和right集合之间的关系

     ①除S以外的每个节点代表一个终点等价类。

     ②每个节点对应的等价类的right集合大小等于以它为根的子树的叶子节点的数量。

     right等价类:

    right{a}={1} 

    right{b}={2,4}

    right{c,bc}={3,5}

    right{ab} ={2}

    right{cb,bcb,abcb} ={4}

    right{abc} ={3}

    right{cbc,bcbc,abcbc}={5}

    我们用s[i]表示i所在的等价类的right集合大小,等于在pre树上以它为根的子树的叶子节点的数量。当sign=1时,s[i]=s[j](jipre树上的孩子);当sign=0时,s[i]=1。对于sign=1的情况,显然孩子节点的step值大于父亲,所以我们只需要按照step值进行拓扑排序,从后往前进行累加即可得到s[i]的值。

    对于叶子节点(叶子节点一定是非后添加的节点,即原字符串中产生的,图中的水平一行),初值在extend中产生:

    1 s[np]=1;

    假设q[i]为拓扑排序后的序列,则如下累加即可:

    1         for(int i=tot;i>=1;i--)
    2         {
    3             if(sign==1)    s[pre[q[i]]]+=s[q[i]];
    4                 else s[q[i]]=1;
    5         }
    6         s[1]=0;//不要忘了根节点是虚点 

     【解题过程】

    ①根据上述知识点中的性质,如何进行拓扑排序呢?

    1 for(int i=1;i<=tot;i++)
    2             v[step[i]]++;//累加每个step[i]的个数 
    3         for(int i=1;i<=tot;i++)
    4             v[i]+=v[i-1];//v[i]表示按step值从小到大的顺序排序后,step值为i的数在q数组中最右端的下标 
    5         for(int i=tot;i>=1;i--)
    6             q[v[step[i]]--]=i;//每次将当前的i放入对应step[i]最右端的位置,然后将step[i]的最右端左移 

    简单地说可以理解为:将当前序列按照step值从小到大排序,对于相同的step值按照原来的出现顺序(下标顺序)从后到前排序。

    如以下情况(实际的后缀自动机中是不会出现下面的例子的,这里仅仅方便理解用)

    i

    1

    2

    3

    4

    5

    6

    7

    Step[i]

    1

    2

    3

    2

    4

    2

    3

    step[i]的前最后和可得到:

    i

    1

    2

    3

    4

    V[i]

    1

    4

    6

    7

     所以相当于得到了这样一张表格:

    step

    1

    2

    2

    2

    3

    3

    4

    i

    1

    2

    3

    4

    5

    6

    7

    q[i]

    ★处即对应上面v[i]的值,表示按step值从小到大的顺序排序后,step值为i的数在q数组中最右端的下标。从后往前依次按照step值填入★处,然后对应的v[step[i]]减一,即将★左移一位。最后我们可以得到这样的结果:

    step

    1

    2

    2

    2

    3

    3

    4

    i

    1

    2

    3

    4

    5

    6

    7

    q[i]

    1

    2

    4

    6

    3

    7

    5

    Sum值代表从当前状态出发不同的路径条数,即将孩子们的路径条数累加起来,再加上本身的s值。即sum[i]=s[i]+sum[j](j=next[i][k],k=0..25)

    1 for(int i=tot;i>=1;i--)
    2         {
    3             sum[q[i]]+=s[q[i]];
    4             for(int j=0;j<26;j++) sum[q[i]]+=sum[next[q[i]][j]];
    5         }

     ③预处理结束之后,通过dfs找出第k小的路径。这有点类似与二十六分,每次先按字典序往后走,如果当前节点的s值大于当前的k,则说明到当前节点为止,退出dfs;否则k先减去当前s的大小。如果当前节点的sum值大于当前的k值,说明终止点再它的孩子中,输出当前节点对应的字母,k并继续往下深dfs;如果当前结点的sum值小于k,说明k大的子串不在这条路径上,直接将k减去sum并继续搜索下一条路径。(说起来有点绕,直接看代码)

     1 if (k<=s[d]) return;
     2          k-=s[d];
     3          for (int i=0;i<26;i++)
     4          {
     5              int tmp=next[d][i];
     6              if (tmp>0)
     7              {
     8                  if (k<=sum[tmp])
     9                  {
    10                      printf("%c",i+'a');
    11                      dfs(tmp,k);
    12                      return;
    13                 }
    14                 k-=sum[tmp];
    15             }
    16         }

    ----搞了好久啊这道题,网上的大家都说是水题,可以得:D那我这个蒟蒻就以非常狼狈的姿势“水”过去好啦。以下代(正)码(文):

      1 #include<iostream>
      2 #include<cstdio>
      3 #include<cstring>
      4 #include<algorithm>
      5 using namespace std;
      6 const int MAXN=500000+50;
      7 char str[MAXN];
      8 int len,sign,k;
      9 
     10 struct SAM
     11 {
     12     int step[MAXN*2],pre[MAXN*2],next[MAXN*2][26],q[MAXN*2];
     13     int v[MAXN*2],s[MAXN*2],sum[MAXN*2];
     14     int tot,last; 
     15     inline int newNode(int cnt)
     16     {
     17         step[++tot]=cnt;
     18         pre[tot]=0;
     19         for (int i=0;i<26;i++) next[tot][i]=0;
     20         return tot;
     21     }
     22     
     23     inline void extend(int x)
     24     {
     25         int p=last;
     26         int np=newNode(step[last]+1);
     27         s[np]=1;
     28         while (p && !next[p][x]) next[p][x]=np,p=pre[p];
     29         if (!p) pre[np]=1;
     30         else
     31         {
     32             int q=next[p][x];
     33             if (step[q]==step[p]+1) pre[np]=q;
     34                 else
     35                 {
     36                     int nq=newNode(step[p]+1);
     37                     for (int i=0;i<26;i++) next[nq][i]=next[q][i];
     38                     pre[nq]=pre[q];
     39                     pre[q]=pre[np]=nq;
     40                     while (p && next[p][x]==q) next[p][x]=nq,p=pre[p]; 
     41                 }
     42         }
     43         last=np;
     44     }
     45     inline void clear()
     46     {
     47         tot=0;
     48         last=newNode(0);
     49     }
     50     
     51     inline void prep()
     52     {
     53         for(int i=1;i<=tot;i++)
     54             v[step[i]]++;//累加每个step[i]的个数 
     55         for(int i=1;i<=tot;i++)
     56             v[i]+=v[i-1];//v[i]表示按step值从小到大的顺序排序后,step值为i的数在q数组中最右端的下标 
     57         for(int i=tot;i>=1;i--)
     58             q[v[step[i]]--]=i;//每次将当前的i放入对应step[i]最右端的位置,然后将step[i]的最右端左移 
     59         for(int i=tot;i>=1;i--)
     60         {
     61             if(sign==1)    s[pre[q[i]]]+=s[q[i]];
     62                 else s[q[i]]=1;
     63         }
     64         s[1]=0;//不要忘了根节点是虚点 
     65         for(int i=tot;i>=1;i--)
     66         {
     67             sum[q[i]]+=s[q[i]];
     68             for(int j=0;j<26;j++) sum[q[i]]+=sum[next[q[i]][j]];
     69         }
     70      }
     71      
     72      inline void dfs(int d,int k)
     73      {
     74          if (k<=s[d]) return;
     75          k-=s[d];
     76          for (int i=0;i<26;i++)
     77          {
     78              int tmp=next[d][i];
     79              if (tmp>0)
     80              {
     81                  if (k<=sum[tmp])
     82                  {
     83                      printf("%c",i+'a');
     84                      dfs(tmp,k);
     85                      return;
     86                 }
     87                 k-=sum[tmp];
     88             }
     89         }
     90      }
     91 }suf;
     92 
     93 void init()
     94 {
     95     scanf("%s",str);
     96     suf.clear();
     97     len=strlen(str);
     98     for (int i=0;i<len;i++) suf.extend(str[i]-'a'); 
     99     scanf("%d%d",&sign,&k);
    100 }
    101 
    102 int main()
    103 {
    104     init();
    105     suf.prep();
    106     suf.dfs(1,k);
    107     return 0;
    108 }
  • 相关阅读:
    进程、线程
    timer控件、三级联动
    用户控件、动态创建添加
    打印控件
    窗体移动和阴影,对话框控件
    winform listview控件
    winform打开唯一窗体、构造函数传值
    菜单和工具栏
    winform公共控件
    hibernate中各种包的添加
  • 原文地址:https://www.cnblogs.com/iiyiyi/p/5737021.html
Copyright © 2011-2022 走看看