LCT+SAM+线段树
LibreOJ开放数据很资瓷啊!
但是我想问为什么首页上我的运势总是凶或者大凶啊???
问一个区间里的前缀之间的最长公共后缀,考虑SAM一发,建出反串后缀树,然后求最长公共后缀就变成求最深的LCA。
直接维护区间的答案不好做,因此考虑一些离线的算法。莫队不知道能不能做,这里考虑把询问按照右端点排序,然后从左到右扫一遍。
扫到i,则维护所有左端点在1~i-1,右端点在i的区间的答案。i新贡献的答案就是i和1~i-1的最公共后缀,也就是后缀树上一个点和其它一些点的最深LCA。考虑在后缀树上的每个节点维护这个节点对应串中位置的最大下标(后面的位置显然不劣于前面的),再维护一个子树最大值。则i往根走的过程中这个值单调不降。暴力枚举每一段来更新答案,用线段树维护区间答案,最后再把根到i的值全设为i即可。
暴力枚举的复杂度不是炸了吗?仔细观察这个过程,发现它是个access,时间复杂度什么就让它去见LCT吧。
#include<cstdio>
#include<vector>
#define N 100005
#define cmax(_i,_j) ((_i)<(_j)?(_i)=(_j):0)
using namespace std;
namespace runzhe2000
{
struct data{int l, id;};
vector<data> que[N];
int n, m, ans[N];
char s[N];
struct seg{int v;}t[N<<2];
void build(int x, int l, int r)
{
t[x].v = 0; if(l == r) return; int mid = (l+r)>>1;
build(x<<1,l,mid); build(x<<1|1,mid+1,r);
}
void modi(int x, int l, int r, int p, int v)
{
if(l == r) return void(cmax(t[x].v, v)); int mid = (l+r)>>1;
p <= mid ? modi(x<<1,l,mid,p,v) : modi(x<<1|1,mid+1,r,p,v);
t[x].v = max(t[x<<1].v, t[x<<1|1].v);
}
int query(int x, int l, int r, int ql, int qr)
{
if(ql <= l && r <= qr) return t[x].v; int mid = (l+r)>>1, ret = 0;
if(ql <= mid) ret = max(ret, query(x<<1,l,mid,ql,qr));
if(mid < qr) ret = max(ret, query(x<<1|1,mid+1,r,ql,qr));
return ret;
}
struct node
{
node *ch[2], *fa;
int v, dep;
}mem[N<<1], *tot, *null;
node *newnode()
{
node *p = ++tot; *p = *null;
return p;
}
int type(node *x){return x->fa->ch[1]==x;}
int isroot(node *x){return x->fa->ch[type(x)] != x;}
void update(node *x)
{
if(!isroot(x)) update(x->fa);
if(x->ch[0] != null) x->ch[0]->v = x->v;
if(x->ch[1] != null) x->ch[1]->v = x->v;
}
void rotate(node *x)
{
node *f = x->fa; int d = type(x);
x->fa = f->fa, !isroot(f) ? x->fa->ch[type(f)] = x : 0;
(f->ch[d] = x->ch[!d]) != null ? f->ch[d]->fa = f : 0;
x->ch[!d] = f, f->fa = x;
}
void splay(node *x)
{
for(update(x); !isroot(x); )
{
if(isroot(x->fa)) rotate(x);
else if(type(x) == type(x->fa)) rotate(x->fa), rotate(x);
else rotate(x), rotate(x);
}
}
void access(node *x)
{
node *tmp = null;
for(; x != null; )
{
splay(x);
if(x->v)
modi(1,1,n,x->v,x->dep);
x->ch[1] = tmp;
tmp = x;
x = x->fa;
}
}
struct sam
{
sam *next[2], *fail;
int len; node *pos;
}mem2[N<<1], *tot2, *null2, *root, *pre[N];
sam *newsam()
{
sam *p = ++tot2; *p = *null2;
return p;
}
void extend(int i)
{
int v = s[i] - '0';
sam *p = pre[i-1], *np = pre[i] = newsam(); np->len = p->len + 1;
for(; p->next[v] == null2 && p != null2; p = p->fail) p->next[v] = np;
if(p == null2) np->fail = root;
else
{
if(p->next[v]->len == p->len + 1) np->fail = p->next[v];
else
{
sam *q = p->next[v], *nq = newsam(); *nq = *q; nq->len = p->len + 1;
q->fail = np->fail = nq;
for(; p->next[v] == q && p != null2; p = p->fail) p->next[v] = nq;
}
}
}
void init()
{
null = tot = mem; *null = (node){null,null,null,0,0};
null2 = tot2 = mem2; *null2 = (sam){{null2,null2},null2,0};
pre[0] = root = newsam();
}
void main()
{
scanf("%d%d%s",&n,&m,s+1);
for(int i = 1; i <= m; i++)
{
int l, r; scanf("%d%d",&l,&r);
que[r].push_back((data){l,i});
}
init(); build(1,1,n);
for(int i = 1; i <= n; i++) extend(i);
for(sam *p = tot2; p != mem2; p--)
{
p->pos = newnode();
p->pos->v = 0;
p->pos->dep = p->len;
}
for(sam *p = tot2; p != root; p--)
{
p->pos->fa = p->fail->pos;
}
for(int i = 1; i <= n; i++)
{
node *p = pre[i]->pos; access(p); splay(p); p->v = i;
for(int j = 0, jj = que[i].size(); j < jj; j++) ans[que[i][j].id] = query(1,1,n,que[i][j].l,n);
}
for(int i = 1; i <= m; i++) printf("%d\n",ans[i]);
}
}
int main()
{
runzhe2000::main();
}