转载: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. 多进程---multiprocessing/threading/

    一.多进程:multiprocessing模块 多用于处理CPU密集型任务 多线程 多用于IO密集型任务 Input Ouput 举例: import multiprocessing,threadin ...

  2. JDK+Tomcat+Eclipse环境搭建过程记录

    这学期选了一门公选课叫网络开发工具与技术,主要学习用JSP语言构建网站.在配置环境的过程中遇到不少的坑,于是记录下来,希望能帮到大家. 系统环境:Win10 JDK版本:8u121, JAVA版本1. ...

  3. 使用NHibernate连接MySQL数据库及增删改查

    学习资料 http://www.sikiedu.com/course/51/task/891/show https://www.codeproject.com/Articles/26123/NHibe ...

  4. eclipse 背景绿豆沙颜色

    General -> Editors -> Text Editors -> Appearance color options -> Background color 色调:85 ...

  5. SSM Maven MallDemo项目为例

    一.创建maven项目 项目结构 创建一个空项目 1. mall (**pom**) 父模块,用于放置公共属性.依赖关系等. 2. mall-util (**jar**) 工具模块,用于放置常用工具类 ...

  6. 内网渗透 - 提权 - Windows

    MS提权 MS16- MS16- 提权框架 Sherlock 信息收集 ifconfig -a cat /etc/hosts arp -a route -n cat /proc/net/* ping扫 ...

  7. linux--初识别

    镜像网站 下载系统镜像 http://mirrors.163.com/ http://mirrors.sohu.com https://www.netcraft.com/ 命令大全 https://m ...

  8. Poj 3268 Silver cow party 迪杰斯特拉+反向矩阵

    Silver cow party 迪杰斯特拉+反向 题意 有n个农场,编号1到n,每个农场都有一头牛.他们想要举行一个party,其他牛到要一个定好的农场中去.每个农场之间有路相连,但是这个路是单向的 ...

  9. [BZOJ 2989]数列(二进制分组+主席树)

    [BZOJ 2989]数列(二进制分组+主席树) 题面 给定一个长度为n的正整数数列a[i]. 定义2个位置的graze值为两者位置差与数值差的和,即graze(x,y)=|x-y|+|a[x]-a[ ...

  10. [BZOJ 3173] [TJOI 2013] 最长上升子序列(fhq treap)

    [BZOJ 3173] [TJOI 2013] 最长上升子序列(fhq treap) 题面 给定一个序列,初始为空.现在我们将1到N的数字插入到序列中,每次将一个数字插入到一个特定的位置.每插入一个数 ...