hdu 4641K-string SAM的O(n^2)算法 以及 SAM+并查集优化
转载: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+并查集优化的更多相关文章
- hdu 4641 K-string SAM的O(n^2)算法 以及 SAM+并查集优化
链接:http://acm.hdu.edu.cn/showproblem.php?pid=4641 题意:有一个长度为n(n < 5e4)的字符串,Q(Q<=2e5)次操作:操作分为:在末 ...
- hdu 1233(还是畅通project)(prime算法,克鲁斯卡尔算法)(并查集,最小生成树)
还是畅通project Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Tota ...
- 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 ...
- HDU 1198 Farm Irrigation (并查集优化,构图)
本题和HDU畅通project类似.仅仅只是畅通project给出了数的连通关系, 而此题须要自己推断连通关系,即两个水管能否够连接到一起,也是本题的难点所在. 记录状态.不断combine(),注意 ...
- HDU 1116 || POJ 1386 || ZOJ 2016 Play on Words (欧拉回路+并查集)
题目链接 题意 : 有很多门,每个门上有很多磁盘,每个盘上一个单词,必须重新排列磁盘使得每个单词的第一个字母与前一个单词的最后一个字母相同.给你一组单词问能不能排成上述形式. 思路 :把每个单词看成有 ...
- 【HDU 3038】 How Many Answers Are Wrong (带权并查集)
How Many Answers Are Wrong Problem Description TT and FF are ... friends. Uh... very very good frien ...
- HDU 3038 How Many Answers Are Wrong 很有意思的一道并查集问题
题目大意:TT 和 FF玩游戏(名字就值五毛),有一个数列,数列有N个元素,现在给出一系列个区间和该区间内各个元素的和,如果后出现的一行数据和前面一出现的数据有矛盾,则记录下来.求有矛盾数据的数量. ...
- hdu 1232 变成生成树至少还要加几条边 (并查集模板题)
求一个图 变成生成树至少还要加几条边(成环的边要删掉,但不用统计) Sample Input4 2 //n m1 3//u v4 33 31 21 32 35 21 23 5999 00 Sample ...
- hdu 5441 (2015长春网络赛E题 带权并查集 )
n个结点,m条边,权值是 从u到v所花的时间 ,每次询问会给一个时间,权值比 询问值小的边就可以走 从u到v 和从v到u算不同的两次 输出有多少种不同的走法(大概是这个意思吧)先把边的权值 从小到大排 ...
随机推荐
- 用Vue来实现音乐播放器(五):路由配置+顶部导航栏组件开发
路由配置 在router文件夹下的index.js中配置路由 import Vue from 'vue' import Router from 'vue-router'//配置路由前先引入组件impo ...
- c#处理bin文件
1. fs.Position 写入的位置,从哪个位置开始写 fs.Write(byte1,0,byte1.Length); byte1写入的byte[], 写入内容从第几位开始取,length取多长 ...
- 解锁 HTTPS原理
From今日头条:https://www.toutiao.com/a6534826865792647693/?tt_from=weixin&utm_campaign=client_share& ...
- jsp+servlet的简单实现
开发环境 tomcat7.0,MyEclipse 10 1.建一个简单的Web Project ,项目名jspServlet: 2.在src目录下建一个package ,为com.fandy.serv ...
- python字典-字典方法
1.kyes() (1)取出字典的key In [32]: myCat Out[32]: {'colr': 'gray', 'size': 'fat'} In [33]: for i in myCat ...
- vue—两个数组,去重相同项
- Numerical Sequence (easy version)
http://codeforces.com/problemset/problem/1216/E1 E1. Numerical Sequence (easy version) time limit pe ...
- 可下拉的PinnedHeaderExpandableListView的实现
版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/singwhatiwanna/article/details/25546871 转载请注明出处:htt ...
- vue数据响应式的一些注意点
有关对象属性值不触发视图更新的情况: Vue 不能检测到对象属性的添加或删除,由于 Vue 会在初始化实例时对属性执行 getter/setter 转化过程,所以属性必须在 data 对象上存在才能让 ...
- C#linq计算总条数并去重复的写法
一,在实际需求中我们会存在选出了一个集合,而这时我们需要通过集合的某几个字段来计算重复,和统计重复的数量,这时我们可以用到linq来筛选和去重复. 二,如下代码: using System; usin ...