链接: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优化没什么卵用。。)

 #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))

代码参考:JeraKrs

 #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 4641 K-string SAM的O(n^2)算法 以及 SAM+并查集优化的更多相关文章

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

    转载:http://www.cnblogs.com/hxer/p/5675149.html 题意:有一个长度为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. K - Find them, Catch them POJ - 1703 (带权并查集)

    题目链接: K - Find them, Catch them POJ - 1703 题目大意:警方决定捣毁两大犯罪团伙:龙帮和蛇帮,显然一个帮派至少有一人.该城有N个罪犯,编号从1至N(N<= ...

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

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

  5. HDU OJ 5326 Work( 2015多校联合训练第3场) 并查集

    题目连接:戳ME #include <iostream> #include <cstdio> #include <cstring> using namespace ...

  6. hdu 并查集分类(待续)

    hdu 1829 A Bug's Life 题目大意: 给你n个动物,输入m行a,b,表示a和b应该是异性的,要你判断是否有同性恋. 并查集中,1到n代表应性别,n+1到2n代表一个性别,合并一下,判 ...

  7. K:Union-Find(并查集)算法

    相关介绍:  并查集的相关算法,是我见过的,最为之有趣的算法之一.并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题.其相关的实现代码较为简短,实现思想也 ...

  8. hdu 1598 (并查集加贪心) 速度与激情

    题目传送门:http://acm.hdu.edu.cn/showproblem.php?pid=1598 一道带有贪心思想的并查集 所以说像二分,贪心这类基础的要掌握的很扎实才行. 用结构体数组储存公 ...

  9. HDU 4641 K-string 后缀自动机 并查集

    http://acm.hdu.edu.cn/showproblem.php?pid=4641 https://blog.csdn.net/asdfgh0308/article/details/4096 ...

随机推荐

  1. Python学习笔记 第二课 循环

    >>> movies=["The Holy Grail", 1975, "The Life of Brian", 1979, "Th ...

  2. Adobe Edge Animate –弹性的方块-使用tweenmax缓动效果

    Adobe Edge Animate –弹性的方块-使用tweenmax缓动效果 版权声明: 本文版权属于 北京联友天下科技发展有限公司. 转载的时候请注明版权和原文地址. 此前有Edge爱好者提出一 ...

  3. [改善Java代码]优先使用整型池

    建议28: 优先使用整型池 看如下代码: public class Client { public static void main(String[] args) { Scanner input = ...

  4. 【转】android应用程序签名

    概述 Android系统要求,所有的程序经过数字签名后才能安装.Android系统使用这个证书来识别应用程序的作者,并且建立程序间的信任关系.证书不是用于用户控制哪些程序可以安装.证书不需要授权中心来 ...

  5. 核心概念 —— 服务容器

    1.简介 Laravel 服务容器是一个用于管理类依赖和执行依赖注入的强大工具.依赖注入听上去很花哨,其实质是通过构造函数或者某些情况下通过 set 方法将类依赖注入到类中. 让我们看一个简单的例子: ...

  6. display:none与visible:hidden区别

    if(list.style.display=='none'){                 list.style.display='block';             }else{       ...

  7. New MVC World

    Note: /Controllers:controllers respond to input from the browser,decide what to do with it,and retur ...

  8. kafka集群安装与配置

    一.集群安装 1. Kafka下载: 可以从kafka官方网站(http://kafka.apache.org)上找到下载地址,再wgetwget http://mirrors.cnnic.cn/ap ...

  9. 转 Android中this、super的区别

    在Java中,this通常指当前对象,super则指父类的.当你想要引用当前对象的某种东西,比如当前对象的某个方法,或当前对象的某个成员,你便可以利用this来实现这个目 的,当然,this的另一个用 ...

  10. python - 图例显示中文

    # -*- coding: utf-8 -*- """ Created on Mon Nov 30 13:24:00 2015 @author: jx_luo " ...