转载:http://www.cnblogs.com/hxer/p/5675149.html

题意:有一个长度为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优化没什么卵用。。)

 #include<iostream>
#include<cstdio>
#include<cstring>
using namespace std; #define maxn 100007
#define SIGMA_SIZE 26 struct SAM{
int sz,tot,last,k;
int g[maxn<<][SIGMA_SIZE],pre[maxn<<],step[maxn<<];
int vs[maxn<<],cnt[maxn<<]; void newNode(int s){
step[++sz] = s;
pre[sz] = ;
vs[sz] = cnt[sz] = ;
memset(g[sz],,sizeof(g[sz]));
} void init(){
tot = ;
sz = ; last = ;
newNode();
} int idx(char ch){return ch - 'a';} void Insert(char ch){
newNode(step[last]+);
int v = idx(ch), p = last, np = sz; while(p && !g[p][v])
g[p][v] = np,p = pre[p]; //知道找到Right集合中包含x的边的祖宗节点 if(p){
int q = g[p][v];
if(step[q] == step[p] + )
pre[np] = q;
else{
newNode(step[p]+);
int nq = sz; //nq替换掉q节点
for(int i = ;i < SIGMA_SIZE;i++)
g[nq][i] = g[q][i]; cnt[nq] = cnt[q]; //**
pre[nq] = pre[q];
pre[np] = pre[q] = nq; while(p && g[p][v] == q)
g[p][v] = nq,p = pre[p];
}
}
else pre[np] = ;
for(int aux = np;aux != && !vs[aux];aux = pre[aux]){
if(++cnt[aux] >= k){
tot += step[aux] - step[pre[aux]];
vs[aux] = true; //该父节点的子串已经加到tot中
}
}
last = np;
}
}SA;
char str[maxn];
int main()
{
int n,Q;
while(scanf("%d%d%d",&n,&Q,&SA.k) == ){
scanf("%s",str);
SA.init();
int len = strlen(str);
for(int i = ;i < len;i++){
SA.Insert(str[i]);
}
int op;
char ch[];
while(Q--){
scanf("%d",&op);
if(op & ){
scanf("%s",ch);
SA.Insert(ch[]);
}
else printf("%d\n",SA.tot);
}
}
}

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

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

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

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

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

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

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

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

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

 #include<iostream>
#include<cstdio>
#include<cstring>
using namespace std; #define maxn 250007
#define SIGMA_SIZE 26
int ans[maxn],op[maxn];
char str[maxn];
int N,Q,K; struct SAM{
int sz,last;
int g[maxn<<][SIGMA_SIZE], pre[maxn<<], step[maxn<<];
int cnt[maxn<<], pos[maxn<<], id[maxn<<];
int f[maxn<<], sub[maxn<<]; int Find(int x){ return f[x] == x? x: f[x] = Find(f[x]); } void init(){
sz = ;last = ;
newNode();
} void newNode(int s){
pre[++sz] = ;
step[sz] = s;
memset(g[sz],,sizeof(g[sz]));
} int idx(char ch){ return ch - 'a'; } void Insert(char ch);
void topoSort();
void getCnt();
void solve(int Q,int *op,int K); }SA; void SAM::Insert(char ch){
newNode(step[last] + );
int v = idx(ch), np = sz, p = last;
id[N] = np;
while(p && !g[p][v]){
g[p][v] = np;
p = pre[p];
} if(p){
int q = g[p][v];
if(step[q] == step[p] + )
pre[np] = q;
else{
newNode(step[p] + );
int nq = sz;
for(int i = ;i < SIGMA_SIZE;i++)
g[nq][i] = g[q][i]; pre[nq] = pre[q];
pre[q] = pre[np] = nq; while(p && g[p][v] == q)
g[p][v] = nq, p = pre[p];
}
}
else pre[np] = ;
last = np;
} void SAM::topoSort(){
for(int i = ; i <= sz; i++) cnt[i] = ;
for(int i = ; i <= sz; i++) cnt[step[i]]++;
for(int i = ; i <= sz; i++) cnt[i] += cnt[i-];
for(int i = ; i <= sz; i++) pos[cnt[step[i]]--] = i;
} void SAM::getCnt(){
for(int i = ; i <= sz; i++) cnt[i] = ;
for(int p = ,i = ; i < N;i++){
int v = idx(str[i]);
p = g[p][v];
cnt[p] = ; //必须是后缀才能赋值root为0
} for(int i = sz; i; i--){
int p = pos[i];
cnt[pre[p]] += cnt[p];
}
} void SAM::solve(int Q,int *op,int K){
long long ret = ;
for(int i = ; i <= sz;i++){
int p = pos[i];
if(cnt[p] >= K) ret += step[p] - step[pre[p]];
} for(int i = ;i <= sz;i++) f[i] = i, sub[i] = ; for(int i = Q; i; i--){
if(op[i] == ) ans[i] = ret;
else{
int p = id[N--];
int fp = Find(p);
while(fp && cnt[fp] < K){
p = f[fp] = pre[fp]; //更新
fp = Find(p); //压缩
}
if(fp == ) continue;
sub[fp]++;
while(fp && cnt[fp] - sub[fp] < K){ //由于单调性 cnt[fp] >= K 是一定成立的
ret -= step[fp] - step[pre[fp]];
p = f[fp] = pre[fp];
sub[pre[fp]] += sub[fp];
fp = Find(p);
}
}
} } int main()
{
while(scanf("%d%d%d",&N,&Q,&K) == ){
scanf("%s",str);
SA.init();
for(int i = ; i < N; i++)
SA.Insert(str[i]);
char aux[];
for(int i = ;i <= Q; i++){
scanf("%d",op + i);
if(op[i] & ){
scanf("%s",aux);
str[N++] = aux[];
SA.Insert(aux[]);
}
}
str[N] = '\0';
SA.topoSort();
SA.getCnt();
SA.solve(Q,op,K); for(int i = ;i <= Q;i++)
if(op[i] == ) printf("%d\n",ans[i]);
}
}

hdu 4641K-string SAM的O(n^2)算法 以及 SAM+并查集优化的更多相关文章

  1. hdu 4641 K-string SAM的O(n^2)算法 以及 SAM+并查集优化

    链接:http://acm.hdu.edu.cn/showproblem.php?pid=4641 题意:有一个长度为n(n < 5e4)的字符串,Q(Q<=2e5)次操作:操作分为:在末 ...

  2. hdu 1233(还是畅通project)(prime算法,克鲁斯卡尔算法)(并查集,最小生成树)

    还是畅通project Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Tota ...

  3. HDU 3038 How Many Answers Are Wrong 【YY && 带权并查集】

    任意门:http://acm.hdu.edu.cn/showproblem.php?pid=3038 How Many Answers Are Wrong Time Limit: 2000/1000 ...

  4. HDU 1198 Farm Irrigation (并查集优化,构图)

    本题和HDU畅通project类似.仅仅只是畅通project给出了数的连通关系, 而此题须要自己推断连通关系,即两个水管能否够连接到一起,也是本题的难点所在. 记录状态.不断combine(),注意 ...

  5. HDU 1116 || POJ 1386 || ZOJ 2016 Play on Words (欧拉回路+并查集)

    题目链接 题意 : 有很多门,每个门上有很多磁盘,每个盘上一个单词,必须重新排列磁盘使得每个单词的第一个字母与前一个单词的最后一个字母相同.给你一组单词问能不能排成上述形式. 思路 :把每个单词看成有 ...

  6. 【HDU 3038】 How Many Answers Are Wrong (带权并查集)

    How Many Answers Are Wrong Problem Description TT and FF are ... friends. Uh... very very good frien ...

  7. HDU 3038 How Many Answers Are Wrong 很有意思的一道并查集问题

    题目大意:TT 和 FF玩游戏(名字就值五毛),有一个数列,数列有N个元素,现在给出一系列个区间和该区间内各个元素的和,如果后出现的一行数据和前面一出现的数据有矛盾,则记录下来.求有矛盾数据的数量. ...

  8. hdu 1232 变成生成树至少还要加几条边 (并查集模板题)

    求一个图 变成生成树至少还要加几条边(成环的边要删掉,但不用统计) Sample Input4 2 //n m1 3//u v4 33 31 21 32 35 21 23 5999 00 Sample ...

  9. hdu 5441 (2015长春网络赛E题 带权并查集 )

    n个结点,m条边,权值是 从u到v所花的时间 ,每次询问会给一个时间,权值比 询问值小的边就可以走 从u到v 和从v到u算不同的两次 输出有多少种不同的走法(大概是这个意思吧)先把边的权值 从小到大排 ...

随机推荐

  1. 用Vue来实现音乐播放器(八):自动轮播图啊

    slider.vue组件的模板部分 <template> <div class="slider" ref="slider"> <d ...

  2. Hand on Machine Learning 第二章:端到端的机器学习

    1.import 模块 import os import tarfile from six.moves import urllib import pandas as pd pd.set_option( ...

  3. sql select as

    as 可理解为:用作.当成,作为:一般式重命名列名或者表名.例如有表table, 列 column_1,column_2 你可以写成 select column_1 as 列1,column_2 as ...

  4. Intersection of Two Linked Lists(两个链表的第一个公共节点)

    来源:https://leetcode.com/problems/intersection-of-two-linked-lists Write a program to find the node a ...

  5. eclipse中常用的快捷键【开发常用到的】

    1.全部选中:Ctrl+A 2.剪切Ctrl+X.复制Ctrl+C.粘贴Ctrl+V.保存Ctrl+S 3.撤销Ctrl+Z.取消撤销Ctrl+Y 4.规范代码:Ctrl+Shift+F 5.将代码更 ...

  6. C# 数据类型之间的转换

    C数据类型转换 https://www.cnblogs.com/bluestorm/p/3168719.html 1 字符串解析为整数: a = int.Parse (Console.ReadLine ...

  7. 循环结构 :while

    循环结构 :while 循环四要素: 1.初始化条件 2.循环条件 3.循环体 4.迭代条件 格式: 1.初始化条件 while(2.循环条件){ 3.循环体 4.迭代条件 } public clas ...

  8. Snacks HDU 5692 dfs序列+线段树

    Snacks HDU 5692 dfs序列+线段树 题意 百度科技园内有n个零食机,零食机之间通过n−1条路相互连通.每个零食机都有一个值v,表示为小度熊提供零食的价值. 由于零食被频繁的消耗和补充, ...

  9. Kosaraju算法 有向图的强连通分量

    有向图的强连通分量即,在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有向图的极 ...

  10. Latex--入门系列一

    Latex 专业的参考 tex对于论文写作或者其他的一些需要排版的写作来说,还是非常有意义的.我在网上看到这个对于Latex的入门介绍还是比较全面的,Arbitrary reference .所以将会 ...