建立后缀树,用线段树合并求出每个节点子树内部最靠前和最靠后的后缀位置以及相邻后缀距离的最大值,同时求出每个子串能完整匹配的最长后缀的长度。

对于一个子串,如果其长度不小于相邻后缀距离的最大值,且最靠后的位置加上最长匹配的后缀长度不小于$n$,那么就说明可以从中间开始覆盖到尾部。

对串做KMP,求出每个前缀的最长border,也就是$nxt$数组。

对于一个子串,设其最早出现的位置为$[l,r]$,那么只要$nxt[r]\geq l-1$,就说明可以覆盖头部。

因为后缀树边经过压缩,所以不能直接枚举所有子串。

对于计数,可以离线之后扫描线树状数组处理。

对于长度最小且字典序最小的解,可以考虑用线段树维护区间内$nxt$的最大值,然后在线段树上二分。

时间复杂度$O(n\log n)$。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
using namespace std;
const int inf=1<<30,S=27,N=200010;
int root,last,pos,need,remain,acnode,ace,aclen;
int n,i,j,nxt[N],f[N<<1],q[N],cnt,first,minl=inf,ansl,ansr;
char text[N],a[N];long long ans;
struct node{int st,en,lk,son[S];int len(){return min(en,pos+1)-st;}}tree[N<<1];
struct E{int x,l,r;E(){}E(int _x,int _l,int _r){x=_x,l=_l,r=_r;}}e[N<<1];
inline bool cmp(int x,int y){return nxt[x]>nxt[y];}
inline bool cmpe(const E&a,const E&b){return a.x>b.x;}
namespace DS{
const int M=3800000;
int tot,l[M],r[M],v[M],vl[M],vr[M],T[N<<1];
int build(int a,int b,int c){
int x=++tot;
vl[x]=vr[x]=c;
if(a==b)return tot;
int mid=(a+b)>>1;
if(c<=mid)l[x]=build(a,mid,c);else r[x]=build(mid+1,b,c);
return x;
}
int merge(int x,int y,int a,int b){
if(!x||!y)return x+y;
int mid=(a+b)>>1;
l[x]=merge(l[x],l[y],a,mid);
r[x]=merge(r[x],r[y],mid+1,b);
vl[x]=l[x]?vl[l[x]]:vl[r[x]];
vr[x]=r[x]?vr[r[x]]:vr[l[x]];
v[x]=max(v[l[x]],v[r[x]]);
if(l[x]&&r[x])v[x]=max(v[x],vl[r[x]]-vr[l[x]]);
return x;
}
}
namespace RangeQuery{
int v[524300],bit[N];
void build(int x,int a,int b){
if(a==b){
v[x]=nxt[a];
return;
}
int mid=(a+b)>>1;
build(x<<1,a,mid),build(x<<1|1,mid+1,b);
v[x]=max(v[x<<1],v[x<<1|1]);
}
void get(int x,int a,int b,int c,int d,int p){
if(first<inf||v[x]<p)return;
if(a==b)first=a;
int mid=(a+b)>>1;
if(c<=mid)get(x<<1,a,mid,c,d,p);
if(d>mid)get(x<<1|1,mid+1,b,c,d,p);
}
inline void add(int x){for(x++;x<=n;x+=x&-x)bit[x]++;}
inline int ask(int x){int t=0;for(;x;x-=x&-x)t+=bit[x];return t;}
inline int sum(int l,int r){return ask(r+1)-ask(l);}
}
int new_node(int st,int en=inf){
node nd;
nd.st=st;nd.en=en;
for(int i=nd.lk=0;i<S;i++)nd.son[i]=0;
tree[++last]=nd;
return last;
}
char acedge(){return text[ace];}
void addedge(int node){
if(need)tree[need].lk=node;
need=node;
}
bool down(int node){
if(aclen>=tree[node].len())return ace+=tree[node].len(),aclen-=tree[node].len(),acnode=node,1;
return 0;
}
void init(){
need=last=remain=ace=aclen=0;
root=acnode=new_node(pos=-1,-1);
}
void extend(char c){
text[++pos]=c;need=0;remain++;
while(remain){
if(!aclen)ace=pos;
if(!tree[acnode].son[acedge()])tree[acnode].son[acedge()]=new_node(pos),addedge(acnode);
else{
int nxt=tree[acnode].son[acedge()];
if(down(nxt))continue;
if(text[tree[nxt].st+aclen]==c){aclen++;addedge(acnode);break;}
int split=new_node(tree[nxt].st,tree[nxt].st+aclen);
tree[acnode].son[acedge()]=split;
tree[split].son[c]=new_node(pos);
tree[nxt].st+=aclen;
tree[split].son[text[tree[nxt].st]]=nxt;
addedge(split);
}
remain--;
if(acnode==root&&aclen)aclen--,ace=pos-remain+1;
else acnode=tree[acnode].lk?tree[acnode].lk:root;
}
}
void dfs(int x,int y,int sum){
sum+=tree[x].len();
if(sum&&min(tree[x].en,pos+1)==pos+1){
if(!tree[x].len())f[y]=max(f[y],sum);
else f[x]=sum;
}
for(int i=0;i<S;i++){
int u=tree[x].son[i];
if(!u)continue;
dfs(u,x,sum);
}
}
inline void solve(int l,int r,int x,int y,int ml){
l=max(l,max(DS::v[DS::T[x]],n-ml-DS::vr[DS::T[x]]));
if(l>r)return;
x=DS::vl[DS::T[x]];
if(l+10>r){
for(int i=l;i<=r;i++)if(nxt[y+i-1]>=x){
ans++;
if(i<minl)minl=i,ansl=y,ansr=y+i-1;
}
return;
}
first=inf;
RangeQuery::get(1,0,n,y+l-1,y+r-1,x);
if(first==inf)return;
first-=y-1;
if(first<minl)minl=first,ansl=y,ansr=y+first-1;
e[cnt++]=E(x,y+l-1,y+r-1);
}
void dfs2(int x,int y,int sum){
int l=sum+1;
sum+=tree[x].len();
if(sum&&min(tree[x].en,pos+1)==pos+1)DS::T[x]=DS::build(0,n,pos-sum+1);
for(int i=0;i<S;i++){
int u=tree[x].son[i];
if(!u)continue;
f[u]=max(f[u],f[x]);
dfs2(u,x,sum);
DS::T[x]=DS::merge(DS::T[x],DS::T[u],0,n);
}
if(l<=sum){
solve(l,sum-1,x,tree[x].st-l+1,f[y]);
solve(sum,sum,x,tree[x].st-l+1,f[x]);
}
}
int main(){
init();
scanf("%s",a);
n=strlen(a);
for(nxt[0]=j=-1,i=1;i<n;nxt[i++]=j){
while(~j&&a[j+1]!=a[i])j=nxt[j];
if(a[j+1]==a[i])j++;
}
for(i=0;i<n;i++)nxt[i]++,q[i]=i;
RangeQuery::build(1,0,n);
for(i=0;i<n;i++)extend(a[i]-'a');extend(26);
pos--;
dfs(root,0,0);
dfs2(root,0,0);
sort(q,q+n,cmp);
if(cnt>1)sort(e,e+cnt,cmpe);
for(i=j=0;i<cnt;i++){
while(j<n&&nxt[q[j]]>=e[i].x)RangeQuery::add(q[j++]);
ans+=RangeQuery::sum(e[i].l,e[i].r);
}
printf("%lld\n",ans);
for(i=ansl;i<=ansr;i++)putchar(a[i]);
return 0;
}

  

BZOJ3499 : PA2009 Quasi-template的更多相关文章

  1. 为.NET Core项目定义Item Template

    作为这个星球上最强大的IDE,Visual Studio不仅仅提供了很多原生的特性,更重要的是它是一个可定制的IDE,比如自定义Project Template和Item Template就是一个非常 ...

  2. jQuery.template.js 简单使用

    之前看了一篇文章<我们为什么要尝试前后端分离>,深有同感,并有了下面的评论: 我最近也和前端同事在讨论这个问题,比如有时候前端写好页面给后端了,然后后端把这些页面拆分成很多的 views, ...

  3. 2000条你应知的WPF小姿势 基础篇<69-73 WPF Freeze机制和Template>

    在正文开始之前需要介绍一个人:Sean Sexton. 来自明尼苏达双城的软件工程师.最为出色的是他维护了两个博客:2,000ThingsYou Should Know About C# 和 2,00 ...

  4. tornado template

    若果使用Tornado进行web开发可能会用到模板功能,页面继承,嵌套... 多页应用模板的处理多半依赖后端(SPA就可以动态加载局部视图),就算是RESTfull的API设计,也不妨碍同时提供部分模 ...

  5. 设计模式(九): 从醋溜土豆丝和清炒苦瓜中来学习"模板方法模式"(Template Method Pattern)

    今天是五.四青年节,祝大家节日快乐.看着今天这标题就有食欲,夏天到了,醋溜土豆丝和清炒苦瓜适合夏天吃,好吃不上火.这两道菜大部分人都应该吃过,特别是醋溜土豆丝,作为“鲁菜”的代表作之一更是为大众所熟知 ...

  6. C++泛型编程:template模板

    泛型编程就是以独立于任何特定类型的方式编写代码,而模板是C++泛型编程的基础. 所谓template,是针对“一个或多个尚未明确的类型”所编写的函数或类. 使用template时,可以显示的或隐示的将 ...

  7. 新手入门Underscore.js 中文(template)

    Underscore.js是一个很精干的库,压缩后只有4KB.它提供了几十种函数式编程的方法,弥补了标准库的不足,大大方便了javaScript的编程.MVC框架Backbone.js就将这个库作为自 ...

  8. knockoutjs如何动态加载外部的file作为component中的template数据源

    玩过knockoutjs的都知道,有一个强大的功能叫做component,而这个component有个牛逼的地方就是拥有自己的viewmodel和template, 比如下面这样: ko.compon ...

  9. JavaScript模板引擎artTemplate.js——template.helper()方法

    上一篇文章我们已经讲到了helper()方法,但是上面的例子只是一个参数的写法,如果是多个参数,写法就另有区别了. <div id="user_info"></d ...

随机推荐

  1. Centos7搭建dhcp服务器

    实验拓扑: 实验步骤如下: 1.挂载本地镜像,并安装dhcp组件. 2.更改配置文件,并重启服务. . 3.配置dhcp地址池范围 4.配置防火墙 结果:在客户端上,重启网卡,后查看ip

  2. Fisher–Yates shuffle 算法

    费希尔 - 耶茨洗牌 维基百科,自由的百科全书     所述费-耶茨洗牌是一种算法,用于产生随机排列的有限的序列 -in平原而言,算法打乱的序列.该算法有效地将所有元素放在帽子里; 它通过随机从帽子中 ...

  3. 使用android-ndk官方ndkbuild例子

    Why this blog 现在(2018年9月27日),Android Studio中新建ndk项目都使用cmake而不是Android.mk+Application.mk的方式.但老项目需要维护, ...

  4. springboot快速使用

    1.编写SpringConfig 用于实例化Spring容器 @Configuration //通过该注解来表明该类是一个Spring的配置,相当于一个xml文件 @Bean // 通过该注解来表明是 ...

  5. Hadoop数据分析平台项目实战(基于CDH版本集群部署与安装)

    1.Hadoop的主要应用场景: a.数据分析平台. b.推荐系统. c.业务系统的底层存储系统. d.业务监控系统. 2.开发环境:Linux集群(Centos64位)+Window开发模式(win ...

  6. [转] react-router4 + webpack Code Splitting

    项目升级为react-router4后,就尝试着根据官方文档进行代码分割.https://reacttraining.com/react-router/web/guides/code-splittin ...

  7. java中String和StringBuffer的区别

    前言 String和StringBuffer本质上都是修饰字符串的只是含义不同 StringBuffer叫做字符串缓冲区 首先看下string类的例子 public class Work1 { pub ...

  8. Derive representation formula from Green’s identity

    This article introduces how to derive the representation formula used in BEM from Green's identity. ...

  9. WebAPI——自动生成帮助文档

    Web Api 自动生成帮助文档   新建Web Api项目之后,会在首页有API的导航菜单,点击即可看到API帮助文档,不过很遗憾,Description 是没有内容的. 怎么办呢? 第一步: 如果 ...

  10. 总结Flink状态管理和容错机制

    本文来自8月11日在北京举行的 Flink Meetup会议,分享来自于施晓罡,目前在阿里大数据团队部从事Blink方面的研发,现在主要负责Blink状态管理和容错相关技术的研发.   本文主要内容如 ...