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\n",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] = '\0';
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\n",ans[i]);
145     }
146 }

 

posted @ 2016-07-16 01:11  hxer  阅读(750)  评论(0编辑  收藏  举报