SAM写的太不熟练了~~SAM上的线段树合并也不熟练~~~

调了半天样例

题目大意:

给定一个S,Q次询问,每次给出T,l,r,

求对于S[l,r],属于T的子串却不属于S[l,r]的子串有多少个

看上去挺简洁的一个问题。。。

暴力68pts

对于S[1,n]68pts?

如果做过

[HEOI2015]最短不公共子串

就好做多了!

可以对A,B分别建SAM

拓扑排序找到A中每个点的后面路径条数。

然后在A上面匹配一遍,如果B匹配不出,直接加上A后面的路径条数

100pts?

刚才的暴力方法实际上不适用了

因为DAG根本无法精确找到[l,r]的部分。。

换一个角度

不从图的路径角度考虑子串了

直接从子串定义考虑

考虑,对于T,[1,i]这个前缀贡献的答案

假设同一个子串可以算多次的话

把[1,i]这个前缀在S[l,r]中匹配,设最长长度是mx

那么贡献的答案就是i-mx

怎么计算"把[1,i]这个前缀在S[l,r]中匹配"得到的最长后缀长度?

用线段树合并维护S的SAM中,点P的right集合

设[1,i-1]匹配的长度为now,匹配在SAM上的点为p

如果p有c出点,出点是x

如果x的right集合中有[l+now,r]区间中一个元素,意味着可以直接匹配下去,得到最长的长度了。break

否则now--,继续尝试。如果now==len[fa[p]],可以更新到更大的集合了,p=fa[p]

设i前缀匹配长度为lim[i]

upda:2019.3.8:

这个匹配本质上是不断找到当前可能的最长后缀now+'c'在S中所有出现位置,然后看这些出现位置有没有末尾在[l+now,r]的

至于相同的子串是1个

那么对T串再建立SAM,用parent树去重,parent树上dfs,每个点的贡献是max(0,min(len[x]-len[fa[x]],len[x]-lim[x]))

相当于把同构的串放在一起,只计算一次

代码

注意,

1.线段树合并还要支持之后的查询

所以必须每次新建节点

类似:CF666E Forensic Examination

2.tot,cnt,num计数器很多别混(懒得namespace了)

#include<bits/stdc++.h>
#define reg register int
#define il inline
#define mid ((l+r)>>1)
#define numb (ch^'0')
using namespace std;
typedef long long ll;
il void rd(int &x){char ch;x=0;bool fl=false;while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);for(x=numb;isdigit(ch=getchar());x=x*10+numb);(fl==true)&&(x=-x);
}
namespace Miracle{
const int N=1e6+5;
const int M=1e6+5;
int n,q;
char s[N];
int lim[M];
ll ans;
struct SAMSAM{int ch[N][26];int len[N],nd,fa[N];int cnt;void init(){cnt=1,nd=1;}struct tr{int ls,rs;int sum;}t[N*20];int rt[N];int tot;void pushup(int x){t[x].sum=t[t[x].ls].sum+t[t[x].rs].sum;}void upda(int &x,int l,int r,int to){x=++tot;if(l==r) {t[x].sum=1;return;}if(to<=mid) upda(t[x].ls,l,mid,to);else upda(t[x].rs,mid+1,r,to);pushup(x);}int merge(int x,int y,int l,int r){//    cout<<" merging "<<x<<" "<<y<<" :: "<<l<<" "<<r<<endl;if(!x||!y) return x+y;int id=++tot;if(l==r){t[id].sum=t[y].sum+t[x].sum;return id;}t[id].ls=merge(t[x].ls,t[y].ls,l,mid);t[id].rs=merge(t[x].rs,t[y].rs,mid+1,r);pushup(id);return id;}void ins(int c,int l){int p=nd;len[nd=++cnt]=l;upda(rt[cnt],1,n,l);for(;p&&ch[p][c]==0;p=fa[p]) ch[p][c]=cnt;//    cout<<"pp "<<p<<" cnt "<<cnt<<" char "<<c<<" ll "<<l<<" : "<<ch[1][c]<<endl;if(!p){fa[cnt]=1;return;}int q=ch[p][c];if(len[q]==len[p]+1){fa[cnt]=q;return;}len[++cnt]=len[p]+1;fa[cnt]=fa[q];fa[q]=fa[nd]=cnt;for(reg j=0;j<26;++j) ch[cnt][j]=ch[q][j];for(;p&&ch[p][c]==q;p=fa[p]) ch[p][c]=cnt;}struct edge{int nxt,to;}e[2*N];int hd[2*N],num;void add(int x,int y){e[++num].nxt=hd[x];e[num].to=y;hd[x]=num;}void build(){//    cout<<" cnt "<<cnt<<endl;for(reg i=2;i<=cnt;++i){//        cout<<i<<" : fafa "<<fa[i]<<endl;
            add(fa[i],i);}}void dfs(int x){//    cout<<" xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx "<<x<<" rt "<<rt[x]<<" sz "<<t[x].sum<<endl;for(reg i=hd[x];i;i=e[i].nxt){int y=e[i].to;dfs(y);rt[x]=merge(rt[x],rt[y],1,n);}}int query(int x,int l,int r,int L,int R){//    cout<<" xx "<<x<<" "<<l<<" and "<<r<<" : query "<<L<<" "<<R<<"  sz "<<t[x].sum<<endl;if(l>r) return 0;if(!x) return 0;if(L<=l&&r<=R) return t[x].sum;int ret=0;if(L<=mid) ret+=query(t[x].ls,l,mid,L,R);if(mid<R) ret+=query(t[x].rs,mid+1,r,L,R);return ret; }
}SAM;
struct samsam{int ch[M][26];int len[M],nd,fa[M];int mx[M];int cnt;void init(){cnt=1,nd=1;}void ins(int c,int l){int p=nd;len[nd=++cnt]=l;mx[cnt]=lim[l];//warning!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!for(;p&&ch[p][c]==0;p=fa[p]) ch[p][c]=cnt;if(!p){fa[cnt]=1;return;}int q=ch[p][c];if(len[q]==len[p]+1){fa[cnt]=q;return;}len[++cnt]=len[p]+1;fa[cnt]=fa[q];fa[q]=fa[nd]=cnt;for(reg j=0;j<26;++j) ch[cnt][j]=ch[q][j];for(;p&&ch[p][c]==q;p=fa[p]) ch[p][c]=cnt;}struct edge{int nxt,to;}e[2*N];int hd[2*N],num;void add(int x,int y){e[++num].nxt=hd[x];e[num].to=y;hd[x]=num;}void build(){for(reg i=2;i<=cnt;++i){add(fa[i],i);}}void dfs(int x){////ans ansnsnannsansansna asn ansfor(reg i=hd[x];i;i=e[i].nxt){int y=e[i].to;dfs(y);mx[x]=max(mx[x],mx[y]);}ans+=max(0,min(len[x]-mx[x],len[x]-len[fa[x]]));}void clear(){for(reg i=1;i<=cnt;++i){for(reg j=0;j<26;++j){ch[i][j]=0;}mx[i]=0;len[i]=0;hd[i]=0;fa[i]=0;}num=0;cnt=1;}
}sam;
void clear(){sam.clear();ans=0;
}
int main(){scanf("%s",s+1);n=strlen(s+1);SAM.init();for(reg i=1;i<=n;++i){SAM.ins(s[i]-'a',i);}
//    cout<<" after ins "<<endl;
    SAM.build();
//    cout<<" after build "<<endl;SAM.dfs(1);
//    cout<<" after dfs "<<endl;
    rd(q);int l,r;while(q--){clear();scanf("%s",s+1);rd(l);rd(r);int len=strlen(s+1);int now=0,p=1;for(reg i=1;i<=len;++i){//pipei int c=s[i]-'a';while(1){//            cout<<" cc "<<c<<" "<<SAM.ch[p][c]<<endl;if(SAM.ch[p][c]&&SAM.query(SAM.rt[SAM.ch[p][c]],1,n,l+now,r)){++now;p=SAM.ch[p][c];break;}    if(!now) break;--now;if(now==SAM.len[SAM.fa[p]]) p=SAM.fa[p];}lim[i]=now;//        cout<<" lim "<<i<<" : "<<lim[i]<<" p "<<p<<endl;
        }sam.init();for(reg i=1;i<=len;++i){//insertsam.ins(s[i]-'a',i);}ans=0;sam.build();sam.dfs(1);printf("%lld\n",ans);}return 0;
}}
signed main(){Miracle::main();return 0;
}/*Author: *Miracle*Date: 2019/1/18 17:48:14
*/

总结:

SAM对于公共子串问题一个基本的方法是跑上去匹配

然后下来再考虑每个位置的贡献

parent树、DAG图无形对子串进行了同构的去重

转载于:https://www.cnblogs.com/Miracevin/p/10289785.html

[NOI2018]你的名字相关推荐

  1. UOJ #395 BZOJ 5417 Luogu P4770 [NOI2018]你的名字 (后缀自动机、线段树合并)

    NOI2019考前做NOI2018题.. 题目链接: (bzoj) https://www.lydsy.com/JudgeOnline/problem.php?id=5417 (luogu) http ...

  2. NOI2010~NOI2018选做

    [NOI2010] [NOI2010]海拔 高度只需要0/1,所以一个合法方案就是一个割,平面图求最小割. [NOI2010]航空管制 反序拓扑排序,每次取出第一类限制最大的放置,这样做答案不会更劣. ...

  3. 一个用SAM维护多个串的根号特技

    一个用SAM维护多个串的根号特技 基本介绍 在多个串的字符串题中,往往会出现一类题需要用到某个子串是否在一些母串中出现.此时对于 \(\text{parent}\) 树的 \(\text{right} ...

  4. oracle退出本循环,Oracle,跳出游标循环

    1,跳出游标的循环,不执行遍历了. 方法一:goto for c_row in 游标 loop if 条件 then dbms_output.put_line('测试跳出循环'); goto brea ...

  5. 【NOI2018】你的名字【后缀自动机】【可持久化线段树合并】【乱搞】

    题意:给一个串 SSS,qqq 次询问,每次给定串 TTT 和 l,rl,rl,r ,求有多少个本质不同的串是 TTT 的子串而不是 Sl-rS_{l\dots r}Sl-r​ 的子串. ∣S∣≤5× ...

  6. 适合计算机应用的班群名称,班级同学群名字大全

    很多人现在都是一个班级建一个群,以便大家沟通交流,有什么事大家群里一说很方便,没事还可以吹吹牛B策策谈,那么同学班级群用什么样的名字好呢,在此起名网为大家收集整理了班级同学群名字大全.来看看吧. 最新 ...

  7. 2020卫星参数表大全_王者荣耀比较秀的名字 2020年比较骚气比较浪的王者荣耀名字大全...

    游戏中该起什么样的名字,才能让其他玩家很快的记住,从而达到认识更多玩家,认识到更多的朋友,达到交友目的. 2020年比较骚气比较浪的王者荣耀男性玩家名字大全如下: 骚里骚气 闷里闷气 孤独患者 洁癖患 ...

  8. C++:名字空间的使用

    首先明确一下要使用名字空间的目的是为了防止名字冲突. 一.访问名字空间中元素的3种方式: 1.可以自己去定义一些名字空间: namespace NameSpace1 {void fun(){}//该f ...

  9. spring 数组中随机取几个_别给孩子取这三种名字!截止年末,名字中的这几个字已经烂大街了...

    随着时代的进步,科技的发展,人们的生活水平不断,提高,许多的家庭都在拥有了不错的生活条件之后选择培养自己的下一代,随着宝宝的到来让整个家庭变得更加幸福,然而,许多家长却在给宝宝起名字这件事上放了愁,对 ...

最新文章

  1. 【转】PHP获取重定向URL的几种方法
  2. 4-20模块 序列化模块 hashlib模块
  3. C#调用Oracle存储过程分页
  4. requests与urllib.request
  5. DL框架之Keras:深度学习框架Keras框架的简介、安装(Python库)、相关概念、Keras模型使用、使用方法之详细攻略
  6. wifi分析仪怎么看哪个信道好_游戏工作室用什么路由器好?合理选择组建手机工作室网络...
  7. 【2018.4.7】模拟赛之五-ssl2386 序列【dp】
  8. 第十节(this关键字 static关键字)
  9. 日志分析代码实现(字符串切割)
  10. 小米 Git(在线笔试)
  11. 跨域支持与返回json数据--mvc访问api接口
  12. 关于目前深度学习技术的相关思考----个人理解笔记
  13. 破解"中国裁判文书网"App加密过程
  14. 【人工智能】传教士和野人问题(M-C问题)
  15. 什么是数字化的马太效应
  16. 第1章 Java基本概念及环境配置——FAQ1.05 Java SE. Java EE和Java ME有什么区别?
  17. c语言实训作业,c语言实训报告评语
  18. 数据库MYSQL及MYSQL ODBC
  19. 网络虚拟试衣间受白领追捧
  20. ABAQUS 建模及分析流程(一)

热门文章

  1. WPF性能调试系列 – 内存监测
  2. 局部内部类访问它所在方法中的局部变量必须是final
  3. ubuntu14.04 LTS 搜狗输入法安装和不能输入中文的解决方法
  4. 【转】C#调用WebService实例和开发
  5. 导出Excel java
  6. sublime插件调用第三方程序
  7. Cookie利用神器:CookieHacker
  8. 【转】ubuntu,你改变了我的人生
  9. 求助关于系统日志的解决方案
  10. 任务 进程 线程区别