zoukankan      html  css  js  c++  java
  • hdu 4641 K-string SAM的O(n^2)算法 以及 SAM+并查集优化

    链接:http://acm.hdu.edu.cn/showproblem.php?pid=4641

    题意:有一个长度为n(n < 5e4)的字符串,Q(Q<=2e5)次操作;操作分为:在末尾插入一个字符ch和查询不同子串出现次数不小于K的数量;

    思路1:SAM在线求解;

    对于每次找到将一个字符x插入到SAM之后,我们知道pre[p]所含有的Tx的后缀字符串数目为step[pre[np]]个,那么只需要每次插入之后更新下这些字符串出现的次数cnt即可;

    由于Right(fa)与Right(r)没有交集(max(fa) = min(r) - 1),所以需要一直递推到root,但是root不能计算,因为root并没有表示后缀,只是一个init状态;

    还有一点就是在拷贝q的信息到nq中时,主要把cnt的信息也拷贝过去;

    由于数据较弱。。当出现5e4长度均为a的字符串,2e5次插入操作;这个算法复杂度将达到O(T*n*Q);

    (因为每插入一个字符,都需要更新5e4次父节点,这时上面的flag优化没什么卵用。。)

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<cstring>
     4 using namespace std;
     5 
     6 #define maxn 100007
     7 #define SIGMA_SIZE 26
     8 
     9 struct SAM{
    10     int sz,tot,last,k;
    11     int g[maxn<<1][SIGMA_SIZE],pre[maxn<<1],step[maxn<<1];
    12     int vs[maxn<<1],cnt[maxn<<1];
    13 
    14     void newNode(int s){
    15         step[++sz] = s;
    16         pre[sz] = 0;
    17         vs[sz] = cnt[sz] = 0;
    18         memset(g[sz],0,sizeof(g[sz]));
    19     }
    20 
    21     void init(){
    22         tot = 0;
    23         sz = 0; last = 1;
    24         newNode(0);
    25     }
    26 
    27     int idx(char ch){return ch - 'a';}
    28 
    29     void Insert(char ch){
    30         newNode(step[last]+1);
    31         int v = idx(ch), p = last, np = sz;
    32 
    33         while(p && !g[p][v])
    34             g[p][v] = np,p = pre[p];    //知道找到Right集合中包含x的边的祖宗节点
    35 
    36         if(p){
    37             int q = g[p][v];
    38             if(step[q] == step[p] + 1)
    39                 pre[np] = q;
    40             else{
    41                 newNode(step[p]+1);
    42                 int nq = sz;             //nq替换掉q节点
    43                 for(int i = 0;i < SIGMA_SIZE;i++)
    44                     g[nq][i] = g[q][i];
    45 
    46                 cnt[nq] = cnt[q];     //**
    47                 pre[nq] = pre[q];
    48                 pre[np] = pre[q] = nq;
    49 
    50                 while(p && g[p][v] == q)
    51                     g[p][v] = nq,p = pre[p];
    52             }
    53         }
    54         else pre[np] = 1;
    55         for(int aux = np;aux != 1 && !vs[aux];aux = pre[aux]){
    56             if(++cnt[aux] >= k){
    57                 tot += step[aux] - step[pre[aux]];
    58                 vs[aux] = true;    //该父节点的子串已经加到tot中
    59             }
    60         }
    61         last = np;
    62     }
    63 }SA;
    64 char str[maxn];
    65 int main()
    66 {
    67     int n,Q;
    68     while(scanf("%d%d%d",&n,&Q,&SA.k) == 3){
    69         scanf("%s",str);
    70         SA.init();
    71         int len = strlen(str);
    72         for(int i = 0;i < len;i++){
    73             SA.Insert(str[i]);
    74         }
    75         int op;
    76         char ch[2];
    77         while(Q--){
    78             scanf("%d",&op);
    79             if(op & 1){
    80                 scanf("%s",ch);
    81                 SA.Insert(ch[0]);
    82             }
    83             else printf("%d
    ",SA.tot);
    84         }
    85     }
    86 }
    View Code

     思路2:SAM离线+并查集优化

    将操作全部插入到SAM并存储之后,先进行拓扑排序;

    1.为什么要进行拓扑排序?

    因为拓扑的目的是为了使得SAM分层,即之后可以使用后缀数组基数排序的思想得到每个节点状态的|Right|即包含的子节点个数;

    思路1由于是在线算法,并不需要知道一个节点的所有子节点(在线+1);

    2.并查集优化哪里? <=> 如何逆向删除末尾加入的字符?

    删除字符其实就是在Insert时存储下来每个字符对应的节点id,之后用并查集Find(p)来得到每次删除时,实际上该节点已经转移到哪个祖先节点的思想;

    并且删除有两次,一次是开始就小于K次,就一次删到大于K次,这时该节点由于一条路径被删了,更改之后看是否也小于K次,循环即可;

    时间复杂度为O(T*(n+m))

    代码参考:JeraKrs

      1 #include<iostream>
      2 #include<cstdio>
      3 #include<cstring>
      4 using namespace std;
      5 
      6 #define maxn 250007
      7 #define SIGMA_SIZE 26
      8 int ans[maxn],op[maxn];
      9 char str[maxn];
     10 int N,Q,K;
     11 
     12 struct SAM{
     13     int sz,last;
     14     int g[maxn<<1][SIGMA_SIZE], pre[maxn<<1], step[maxn<<1];
     15     int cnt[maxn<<1], pos[maxn<<1], id[maxn<<1];    
     16     int f[maxn<<1], sub[maxn<<1];
     17 
     18     int Find(int x){ return f[x] == x? x: f[x] = Find(f[x]); }
     19 
     20     void init(){
     21         sz = 0;last = 1;
     22         newNode(0);
     23     }
     24 
     25     void newNode(int s){
     26         pre[++sz] = 0;
     27         step[sz] = s;
     28         memset(g[sz],0,sizeof(g[sz]));
     29     }
     30 
     31     int idx(char ch){ return ch - 'a'; }
     32 
     33     void Insert(char ch);
     34     void topoSort();
     35     void getCnt();
     36     void solve(int Q,int *op,int K);
     37 
     38 }SA;
     39 
     40 void SAM::Insert(char ch){
     41     newNode(step[last] + 1);
     42     int v = idx(ch), np = sz, p = last;
     43     id[N] = np;
     44     while(p && !g[p][v]){
     45         g[p][v] = np;
     46         p = pre[p];
     47     }
     48 
     49     if(p){
     50         int q = g[p][v];
     51         if(step[q] == step[p] + 1)
     52             pre[np] = q;
     53         else{
     54             newNode(step[p] + 1);
     55             int nq = sz;
     56             for(int i = 0;i < SIGMA_SIZE;i++)
     57                 g[nq][i] = g[q][i];
     58 
     59             pre[nq] = pre[q];
     60             pre[q] = pre[np] = nq;
     61 
     62             while(p && g[p][v] == q)
     63                 g[p][v] = nq, p = pre[p];
     64         }
     65     }
     66     else pre[np] = 1;
     67     last = np;
     68 }
     69 
     70 void SAM::topoSort(){   
     71     for(int i = 0; i <= sz; i++) cnt[i] = 0;
     72     for(int i = 1; i <= sz; i++) cnt[step[i]]++;
     73     for(int i = 1; i <= sz; i++) cnt[i] += cnt[i-1];
     74     for(int i = 1; i <= sz; i++) pos[cnt[step[i]]--] = i;
     75 }
     76 
     77 void SAM::getCnt(){
     78     for(int i = 0; i <= sz; i++) cnt[i] = 0;
     79     for(int p = 1,i = 0; i < N;i++){
     80         int v = idx(str[i]);
     81         p = g[p][v];
     82         cnt[p] = 1;   //必须是后缀才能赋值root为0
     83     }
     84 
     85     for(int i = sz; i; i--){
     86         int p = pos[i];
     87         cnt[pre[p]] += cnt[p];
     88     }
     89 }
     90 
     91 void SAM::solve(int Q,int *op,int K){
     92     long long ret = 0;
     93     for(int i = 1; i <= sz;i++){
     94         int p = pos[i];
     95         if(cnt[p] >= K) ret += step[p] - step[pre[p]];
     96     }
     97 
     98     for(int i = 1;i <= sz;i++) f[i] = i, sub[i] = 0;
     99 
    100     for(int i = Q; i; i--){
    101         if(op[i] == 2) ans[i] = ret;
    102         else{
    103             int p = id[N--];
    104             int fp = Find(p);
    105             while(fp && cnt[fp] < K){
    106                 p = f[fp] = pre[fp];    //更新
    107                 fp = Find(p);           //压缩
    108             }
    109             if(fp == 0) continue;
    110             sub[fp]++;
    111             while(fp && cnt[fp] - sub[fp] < K){ //由于单调性 cnt[fp] >= K 是一定成立的
    112                 ret -= step[fp] - step[pre[fp]];
    113                 p = f[fp] = pre[fp];
    114                 sub[pre[fp]] += sub[fp];
    115                 fp = Find(p);
    116             }
    117         }
    118     }
    119 
    120 }
    121 
    122 int main()
    123 {
    124     while(scanf("%d%d%d",&N,&Q,&K) == 3){
    125         scanf("%s",str);
    126         SA.init();
    127         for(int i = 0; i < N; i++)
    128             SA.Insert(str[i]);
    129         char aux[2];
    130         for(int i = 1;i <= Q; i++){
    131             scanf("%d",op + i);
    132             if(op[i] & 1){
    133                 scanf("%s",aux);
    134                 str[N++] = aux[0];
    135                 SA.Insert(aux[0]);
    136             }
    137         }
    138         str[N] = '';
    139         SA.topoSort();
    140         SA.getCnt();
    141         SA.solve(Q,op,K);
    142 
    143         for(int i = 1;i <= Q;i++)
    144             if(op[i] == 2) printf("%d
    ",ans[i]);
    145     }
    146 }
  • 相关阅读:
    wp8 入门到精通
    C# 从入门到精通
    wp8 json2csharp
    wp8 安装.Net3.5
    delphi资源文件制作及使用详解
    delphi弹出选择对话框选择目录SelectDirectory 函数
    delphi 判断WIN8 , WIN8.1 , WIN10 系统版本
    外壳扩展创建快捷方式和获取快捷方式的目标对象
    文本究竟是使用哪种字
    用Delphi创建服务程序
  • 原文地址:https://www.cnblogs.com/hxer/p/5675149.html
Copyright © 2011-2022 走看看