转载: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. SpringBoot 集成mongodb(2)多数据源配置

    github:https://github.com/xiaozhuanfeng/mongoProj 现MongoDB有两个数据库: pom.xml: <!-- mongodb 配置 --> ...

  2. 010-elasticsearch5.4.3【四】-聚合操作【一】-度量聚合【metrics】-min、max、sum、avg、count

    一.概述 度量类型聚合主要针对的number类型的数据,需要ES做比较多的计算工作 参考向导:地址 import org.elasticsearch.search.aggregations.Aggre ...

  3. 快速入门分布式消息队列之 RabbitMQ(1)

    目录 目录 前言 简介 安装 RabbitMQ 基本对象概念 Message 消息 Producer 生产者 Consumer 消费者 Queue 队列 Exchange 交换机 Binding 绑定 ...

  4. 红帽虚拟化RHEV-PXE批量安装RHEV-H

    目录 目录 前言 软件环境 前提 部署 PXE 环境 使用 yum 安装所需软件 配置 DHCP 配置 TFTP-Server 配置 vsftpd 服务用于提供安装系统所需软件包 安装 kicksta ...

  5. Charls

    1.连接设备 charles--proxy--proxy setting 设置端口号 charles--proxy--ssl proxy setting 设置代理域名 [pc端] charles--p ...

  6. Leveldb--Slice

    http://www.kuqin.com/database/20110919/265041.html Slice非常简单的数据结构,它包括length和一个指向外部字节数组的指针.为什么使用Slice ...

  7. sql server 中 like 中文不匹配问题

    原文:https://blog.csdn.net/miao0967020148/article/details/71108056 MS-SQL Server select * from Book wh ...

  8. C# XML 解析包含特殊字符的内容

    xml结构会解析一些特殊字符,特别是& <    ,导致XmlDocument 解析错误 解决方法:将内容放在<![CDATA[ ]]>中,例如<![CDATA[2]] ...

  9. JVM内存模型及参数调优

    堆.栈.方法区概念区别 1.堆 堆内存用于存放由new创建的对象和数组.在堆中分配的内存,由java虚拟机自动垃圾回收器来管理.根据垃圾回收机制的不同, Java堆有可能拥有不同的结构,最为常见的就是 ...

  10. websocket在springboot+vue中的使用

    1.websocket在springboot中的一种实现 在java后台中,websocket是作为一种服务端配置,其配置如下 @Configuration public class WebSocke ...