E. Forensic Examination

http://codeforces.com/problemset/problem/666/E

题目大意:给模式串S以及m个特殊串,q个询问,询问S的子串[pl,pr]在特殊串编号属于[l,r]中出现次数最多的次数以及在哪个特殊串。

一开始打算用S建SAM,特殊串去匹配。。。测样例时才想起这样不对。胡搞一番后,才开始写下面的做法。

S与特殊串连在一起建SAM,记录S串[1,x]在SAM上节点位置,用来子串定位。每个节点的{right}该串出现的所有地方。于是题目成了求一个节点的{right}中属于某个特殊串次数最多的特殊串。给这些{right}染色,即标记属于哪个特殊串。一个节点用一棵线段树维护最值,然后用线段树合并求出每个节点的线段树。

复杂度O(nlogn)。。。这n大概为106

#include<cstdio>
#include<cmath>
#include<cstring>
const int len(),lem(),N();
struct Node{int pre,nx[],step;}sam[N*+];
int top=,now=,root=,last,lastson;int n,pos[len+];
int m,q,leng,l,r,pl,pr;char str[len+];
int f[][N*+],logg;
struct SegMent{int nx[];unsigned short int big,pos;}tree[lem+];
int stot,rt[N*+];
struct data{unsigned short int x,y;};
void extend(int x)
{
last=now;now=++top;sam[now].step=sam[last].step+;
for(;!sam[last].nx[x]&&last;last=sam[last].pre)
sam[last].nx[x]=now;
if(!last)sam[now].pre=root;
else
{
lastson=sam[last].nx[x];
if(sam[lastson].step==sam[last].step+)sam[now].pre=lastson;
else
{
sam[++top]=sam[lastson];sam[top].step=sam[last].step+;
sam[now].pre=sam[lastson].pre=top;
for(;sam[last].nx[x]==lastson&&last;last=sam[last].pre)
sam[last].nx[x]=top;
}
}
}
void update(int k)
{
int next;
if(tree[tree[k].nx[]].big>tree[tree[k].nx[]].big||
(tree[tree[k].nx[]].big==tree[tree[k].nx[]].big&&
tree[tree[k].nx[]].pos<tree[tree[k].nx[]].pos))next=;
else next=;
tree[k].big=tree[tree[k].nx[next]].big;
tree[k].pos=tree[tree[k].nx[next]].pos;
}
void insert(int &k,int l,int r,int x)
{
if(!k)k=++stot;
if(l==r){tree[k].big++;tree[k].pos=x;return;}
int mid=(l+r)>>;
if(x<=mid)insert(tree[k].nx[],l,mid,x);
else insert(tree[k].nx[],mid+,r,x);
update(k);
}
int find(int l,int r)
{
int x=pos[r];
for(int j=logg;j>=;j--)
if(sam[f[j][x]].step>=r-l+)x=f[j][x];
return x;
}
int merge(int x,int y,int l,int r)
{
if(!x||!y)return x|y;
int z=++stot;
if(l==r){tree[z].big=tree[x].big+tree[y].big;tree[z].pos=tree[x].pos;return z;}
int mid=(l+r)>>;
tree[z].nx[]=merge(tree[x].nx[],tree[y].nx[],l,mid);
tree[z].nx[]=merge(tree[x].nx[],tree[y].nx[],mid+,r);
update(z);
return z;
}
int cnt[N*+],p[N*+];
void update()
{
for(int i=;i<=top;i++)cnt[sam[i].step]++;
for(int i=;i<=top;i++)cnt[i]+=cnt[i-];
for(int i=top;i>=;i--)p[cnt[sam[i].step]--]=i;
for(int i=top;i>=;i--)
rt[sam[p[i]].pre]=merge(rt[sam[p[i]].pre],rt[p[i]],,m);
}
void deal()
{
logg=log(top)/log();
for(int i=;i<=top;i++)f[][i]=sam[i].pre;
for(int j=;j<=logg;j++)
for(int i=;i<=top;i++)
f[j][i]=f[j-][f[j-][i]];
}
data query(int k,int l,int r,int L,int R)
{
if(!k)return (data){,l};
if(L==l&&R==r)return (data){tree[k].big,tree[k].pos};
int mid=(L+R)>>;
if(r<=mid)return query(tree[k].nx[],l,r,L,mid);
else if(mid<l)return query(tree[k].nx[],l,r,mid+,R);
else
{
data t1=query(tree[k].nx[],l,mid,L,mid),t2=query(tree[k].nx[],mid+,r,mid+,R);
if(t1.x>t2.x||(t1.x==t2.x&&t1.y<t2.y))return t1;
else return t2;
}
}
void read(int &x)
{
x=;char ch=getchar();
while(ch<''||ch>'')ch=getchar();
while(ch>=''&&ch<='')x=x*+ch-'',ch=getchar();
}
int main()
{
freopen("C.in","r",stdin);
freopen("C2.out","w",stdout);
scanf("%s",str);
n=strlen(str);
for(int i=;i<n;i++)extend(str[i]-'a'),pos[i+]=now;
extend();
scanf("%d",&m);
for(int i=;i<=m;i++)
{
scanf("%s",str);leng=strlen(str);
for(int j=;j<leng;j++)extend(str[j]-'a'),insert(rt[now],,m,i);
extend();
}
deal();update();
scanf("%d",&q);
for(int i=;i<=q;i++)
{
scanf("%d%d%d%d",&l,&r,&pl,&pr);
int x=find(pl,pr);data tmp;
tmp=query(rt[x],l,r,,m);
if(tmp.x==)tmp.y=l;
printf("%d %d\n",tmp.y,tmp.x);
}
return ;
}

第二种写法:

将串连在一起求SA,设L为小于rank[pl]的第一个height[L]=pr-pl+1,R为大于rank[pl]的第一个height[R]=pr-pl+1。那么[pl,pr]能匹配的后缀区间即[L,R]。给后缀标记一下属于哪个特殊串,将题目变成了求区间众数。有趣的是这些区间不是包含关系就是相离关系。
令height[i]=(i-1,i)的(无向)边权,那么一次询问就是询问点rank[pl]只经过边权大于等于(pr-pl+1)的边所能到达点中出现次数最多的特殊串。
于是变成B3545Peaks加强版,用线段树维护,线段树合并实现。

O(nlogn)

#include<cstdio>
#include<cmath>
#include<cstring>
#include<vector>
const int limt(),len(),lem();
int tmp[len+],cnt[len+],p[len+];
int rank[len*+],sfa[len+],height[len+];
int str[len+],color[len+],val[len*+];
int n,m,Q,leng,l,r,pl,pr,x;char ch[len+];
struct Node{int nd,nx,co;}bot[len*+];int tot,first[len*+];
int g[][len*+],f[][len*+],logg;
int father[len*+],now,last;
std::vector<int>weight[len+];
struct ANS
{
unsigned short int big,pos;
inline ANS operator +(const ANS&A)const{return (ANS){big+A.big,pos};}
inline ANS operator *(const ANS&A)const{if(A.big<big||(big==A.big&&pos<A.pos))return (ANS){big,pos};return A;}
}ans;
struct SegMent{int nx[];ANS key;}tree[lem+];int stot,root[len*+];
void read(int &x)
{
x=;int f=;char ch=getchar();
while((ch<''||ch>'')&&(ch!='-')){ch=getchar();}if(ch=='-')f=-,ch=getchar();
while(ch>=''&&ch<=''){x=x*+ch-'';ch=getchar();}
if(f<)x=-x;
}
void add(int a,int b,int c){bot[++tot]=(Node){b,first[a],c};first[a]=tot;}
void swap(int &a,int &b){if(a==b)return;a^=b;b^=a;a^=b;}
int min(int a,int b){return a>b?b:a;} bool com(int x,int y,int l){return rank[x]==rank[y]&&rank[x+l]==rank[y+l];}
void doubling()
{
for(int i=;i<=n;i++)rank[i]=str[i],sfa[i]=i;
for(int pos=,l=,sigma=limt;pos<n;sigma=pos)
{
pos=;
for(int i=n-l+;i<=n;i++)p[++pos]=i;
for(int i=;i<=n;i++)if(sfa[i]>l)p[++pos]=sfa[i]-l;
memset(cnt,,sizeof(int)*(sigma+));pos=;
for(int i=;i<=n;i++)cnt[rank[i]]++;
for(int i=;i<=sigma;i++)cnt[i]+=cnt[i-];
for(int i=n;i>=;i--)sfa[cnt[rank[p[i]]]--]=p[i];
for(int i=;i<=n;i++)tmp[sfa[i]]=com(sfa[i],sfa[i-],l)?pos:++pos;
for(int i=;i<=n;i++)rank[i]=tmp[i];
l=!l?:l<<;
}
for(int i=;i<=n;i++)rank[sfa[i]]=i;
for(int i=,j,k;i<=n;i++)
{
k=sfa[rank[i]-]; if(!k)continue;
j=height[rank[i-]]; if(j)j--;
while(str[i+j]==str[k+j])
j++;
height[rank[i]]=j;
}
}
int gf(int x)
{
int v=x,o;while(v!=father[v])v=father[v];
for(;x!=father[x];x=o){o=father[x];father[x]=v;}
return v;
}
void exchange()
{
for(int i=;i<=n;i++)weight[height[i]].push_back(i);
for(int i=;i<=n;i++)val[i]=color[sfa[i]];
for(int i=;i<=n*;i++)father[i]=i; now=n;last=now;
for(int i=n,fa,fb;i>=;i--)
{
last=now;
for(int v=;v<(int)weight[i].size();v++)
{
int t=weight[i][v];
fa=gf(weight[i][v]-);fb=gf(weight[i][v]);
if(fa!=fb)
{
now++;
father[fb]=father[fa]=father[now];
if(now!=fb)add(now,fb,i);
if(now!=fa)add(now,fa,i);
}
}
}
now++;
for(int i=,fa;i<=n;i++)
{
fa=gf(i);
if(fa!=now)add(now,fa,);father[fa]=now;
}
}
void update(int k){tree[k].key=tree[tree[k].nx[]].key*tree[tree[k].nx[]].key;}
void insert(int &k,int l,int r,int x)
{
if(!k)k=++stot;
if(l==r){tree[k].key.big++;tree[k].key.pos=l;return;}
int mid=(l+r)>>;
if(x<=mid)insert(tree[k].nx[],l,mid,x);
else insert(tree[k].nx[],mid+,r,x);
update(k);
}
int merge(int x,int y,int l,int r)
{
if(!x||!y)return (x|y);
int z=++stot,mid=(l+r)>>;
if(l==r){tree[z].key=tree[x].key+tree[y].key;return z;}
tree[z].nx[]=merge(tree[x].nx[],tree[y].nx[],l,mid);
tree[z].nx[]=merge(tree[x].nx[],tree[y].nx[],mid+,r);
update(z);
return z;
}
void dfs(int x)
{
if(val[x])insert(root[x],,m,val[x]);
for(int v=first[x];v;v=bot[v].nx)
{
dfs(bot[v].nd); f[][bot[v].nd]=x; g[][bot[v].nd]=bot[v].co;
root[x]=merge(root[x],root[bot[v].nd],,m);
// fprintf(stdout,"%d\n",stot);
}
}
void Fdeal()
{
logg=log(now)/log();
for(int j=;j<=logg;j++)
for(int i=;i<=now;i++)
f[j][i]=f[j-][f[j-][i]],g[j][i]=min(g[j-][i],g[j-][f[j-][i]]);
}
int find(int pl,int pr)
{
int x=rank[pl];
for(int j=logg;j>=;j--)
if(g[j][x]>=pr-pl+)x=f[j][x];
return x;
}
ANS query(int k,int L,int R,int l,int r)
{
if(!k)return (ANS){,l};
if(l==L&&r==R)return tree[k].key;
int mid=(L+R)>>;
if(r<=mid)return query(tree[k].nx[],L,mid,l,r);
else if(mid<l)return query(tree[k].nx[],mid+,R,l,r);
else return query(tree[k].nx[],L,mid,l,mid)*query(tree[k].nx[],mid+,R,mid+,r);
}
int main()
{
// freopen("C.in","r",stdin);
// freopen("C.out","w",stdout);
scanf("%s",ch);
leng=strlen(ch);
for(int i=;i<leng;i++)str[++n]=ch[i]-'a'+;
str[++n]=; scanf("%d",&m);
for(int i=;i<=m;i++)
{
scanf("%s",ch);
leng=strlen(ch);
for(int j=;j<leng;j++)str[++n]=ch[j]-'a'+,color[n]=i;
str[++n]=;
}
doubling();
exchange();//将序列变成树
dfs(now);
Fdeal();
scanf("%d",&Q);
for(int i=;i<=Q;i++)
{
read(l);read(r);read(pl);read(pr);
x=find(pl,pr);
ans=query(root[x],,m,l,r);
printf("%d %d\n",ans.pos,ans.big);
}
return ;
}

Codeforces 666E Forensic Examination SAM or SA+线段树合并的更多相关文章

  1. Codeforces 666E Forensic Examination SAM+权值线段树

    第一次做这种$SAM$带权值线段树合并的题 然而$zjq$神犇看完题一顿狂码就做出来了 $Orz$ 首先把所有串当成一个串建$SAM$ 我们对$SAM$上每个点 建一棵权值线段树 每个叶子节点表示一个 ...

  2. Codeforces.666E.Forensic Examination(广义后缀自动机 线段树合并)

    题目链接 \(Description\) 给定串\(S\)和\(m\)个串\(T_i\).\(Q\)次询问,每次询问\(l,r,p_l,p_r\),求\(S[p_l\sim p_r]\)在\(T_l\ ...

  3. CF 666E Forensic Examination——广义后缀自动机+线段树合并

    题目:http://codeforces.com/contest/666/problem/E 对模式串建广义后缀自动机,询问的时候把询问子串对应到广义后缀自动机的节点上,就处理了“区间”询问. 还要处 ...

  4. CF666E Forensic Examination SAM+倍增,线段树和并

    题面: 给你一个串S以及一个字符串数组T[1..m],q次询问,每次问S的子串S[p_l..p_r]在T[l..r]中的哪个串里的出现次数最多,并输出出现次数.如有多解输出最靠前的那一个. 分析: 第 ...

  5. CF666E Forensic Examination [后缀自动机,线段树合并]

    洛谷 Codeforces 思路 最初的想法:后缀数组+区间众数,似乎并不能过. 既然后缀数组不行,那就按照套路建出广义SAM,然后把\(S\)放在上面跑,得到以每个点结尾会到SAM上哪个节点. 询问 ...

  6. cf666E. Forensic Examination(广义后缀自动机 线段树合并)

    题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并 首先对所有的\(t_i\)建个广义后缀自动机,这样可以得到所有子串信息. 考虑把询问离线,然后把\(S\)拿到自动机上跑,同时维护一下 ...

  7. 【CF666E】Forensic Examination - 广义后缀自动机+线段树合并

    广义SAM专题的最后一题了……呼 题意: 给出一个长度为$n$的串$S$和$m$个串$T_{1\cdots m}$,给出$q$个询问$l,r,pl,pr$,询问$S[pl\cdots pr]$在$T_ ...

  8. CF666E Forensic Examination(后缀自动机+线段树合并)

    给你一个串S以及一个字符串数组T[1..m],q次询问,每次问S的子串S[pl..pr]在T[l..r]中的哪个串里的出现次数最多,并输出出现次数. 如有多解输出最靠前的那一个. 我们首先对m个字符串 ...

  9. [CF666E]Forensic Examination:后缀自动机+线段树合并

    分析 用到了两个小套路: 使用线段树合并维护广义后缀自动机的\(right\)集合. 查询\(S[L,R]\)在\(T\)中的出现次数:给\(T\)建SAM,在上面跑\(S\),跑到\(R\)的时候先 ...

随机推荐

  1. C++ Queues(队列)

    C++ Queues(队列) C++队列是一种容器适配器,它给予程序员一种先进先出(FIFO)的数据结构.1.back() 返回一个引用,指向最后一个元素2.empty() 如果队列空则返回真3.fr ...

  2. Fitnesse框架简单介绍

    1.Fitnesse是什么? 官方的说明:FitNesse is a wiki server. It's also a test execution engine. Fitnesse是一个wiki s ...

  3. Git 分支管理 不使用Fast forward模式进行合并 分支管理策略

    通常,合并分支时,如果可能,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息. 如果要强制禁用Fast forward模式,Git就会在merge时生成一个新的comm ...

  4. spring配置与使用

    1. 创建基于java的配置. 配置极少量的XML来启用java配置: <?xml version="1.0" encoding="UTF-8"?> ...

  5. hiho week 136(二分+优先队列)

    题目链接:http://hihocoder.com/contest/hiho136/problem/1 题意:中文题诶- 思路:直接对缓存区长度二分就好了,注意计算当前长度的"延迟惩罚值&q ...

  6. MySQL下载与安装配置

    一.Windows 上安装 MySQL Windows 上安装 MySQL 相对简单,最新版本下载地址: 官网:https://dev.mysql.com/downloads/mysql/ 下载步骤: ...

  7. GFS安装

    GlusterFS 搭建 1.环境要求 IP地址 主机名称 系统 172.16.2.201 test01 Centos 6.4 172.16.2.202 test02 Centos 6.4 172.1 ...

  8. Windows服务器初始配置

    系统状态是否良好 检查ip地址.子网掩码等配置 防火墙是否关闭 是否有攻击,有多大的攻击,什么类型的攻击,攻击流量图 是否中病毒 1.改端口 (1)打开注册表 [HKEY_LOCAL_MACHINE\ ...

  9. day5字典作业详解

    1.day5题目 1.有如下变量(tu是个元祖),请实现要求的功能 tu = ("alex", [11, 22, {"k1": 'v1', "k2&q ...

  10. 如何使用sass

    Sass 是对 CSS 的扩展,让 CSS 语言更强大.优雅. 它允许你使用变量.嵌套规则.mixin.导入等众多功能, 并且完全兼容 CSS 语法. Sass 有助于保持大型样式表结构良好, 同时也 ...