首页 > 技术文章 > LOJ 6041 事情的相似度

river-flows-in-you 2019-12-03 08:28 原文

LOJ 6041 事情的相似度

题目描述

人的一生不仅要靠自我奋斗,还要考虑到历史的行程。

历史的行程可以抽象成一个 01 串,作为一个年纪比较大的人,你希望从历史的行程中获得一些姿势。

你发现在历史的不同时刻,不断的有相同的事情发生。比如,有两个人同时在世纪之交 11 年的时候上台,同样喜欢与洋人谈笑风生,同样提出了以「三」字开头的理论。

你发现,一件事情可以看成是这个 01 串的一个前缀,这个前缀最右边的位置就是这个事情的结束时间。

两件事情的相似度可以看成,这两个前缀的最长公共后缀长度。

现在你很好奇,在一段区间内结束的事情中最相似的两件事情的相似度是多少呢?

数据范围:\(n,m\le 10^5\)

思路

首先,建一个后缀自动机。

那么,两件事情的相似度就是他们\(parent\)树上的最近公共祖先的深度。

我们考虑把所有询问离线下来,按照右端点排序。

那么,每次查询时,我们只需要知道\([l,r]\)中每个点到他后面的点的贡献,再取\(max\)即可

我们考虑,每次加入一个点的时候,把这个点到根的路径全部染上他的颜色。

每遇见一种颜色时,更新这个颜色的答案。

首先发现,如此操作与我们理论上要求的略有不同,因为后面的颜色会覆盖前面的颜色,就导致前面的颜色的答案并没有更新。

但是再仔细想想,这对答案并不影响。因为我们是从前到后把点加进来的,所以说如果你能覆盖前面的颜色,也一定能覆盖后面的颜色。

于是我们用类似\(LCT\)\(access\)操作维护即可。

代码

#include<bits/stdc++.h>
#define lch ch[x][0]
#define rch ch[x][1]
#define now c[top].nxt[k]
using namespace std;
const int sz=2e5+7;
int n,m;
int cnt,L,R;
int rt,lst,cur,top;
int f[sz],col[sz];
int pos[sz];
int mx[sz],ans[sz];
int ch[sz][2];
char s[sz];
struct node{
	int len,link,nxt[2];
}c[sz];
struct Que{
	int l,r,id;
	const bool operator <(const Que &p)const{
		return r<p.r;
	}
}q[sz];
void update(int x,int sum){
	for(;x;x-=x&-x) mx[x]=max(mx[x],sum);
}
int query(int x){
	int ret=0;
	for(;x<=n;x+=x&-x) ret=max(ret,mx[x]);
	return ret;
}
bool nroot(int x){
	return ch[f[x]][0]==x||ch[f[x]][1]==x;
}
int get(int x){
	return ch[f[x]][1]==x;
}
void pushdn(int x){
	if(lch) col[lch]=col[x];
	if(rch) col[rch]=col[x];
}
void pushall(int x){
	if(nroot(x)) pushall(f[x]);
	pushdn(x);
}
void rotate(int x){
	int y=f[x],z=f[y],k=get(x),w=ch[x][!k];
	if(nroot(y)) ch[z][get(y)]=x;ch[x][!k]=y;ch[y][k]=w;
	if(w) f[w]=y;f[y]=x;f[x]=z;
}
void splay(int x){
	pushall(x);
	for(int y;nroot(x);rotate(x)) if(nroot(y=f[x])) rotate(get(x)^get(y)?x:y);
}
void access(int x,int cc){
	for(int y=0;x;x=f[y=x]){
		splay(x);
		update(col[x],c[x].len);
		col[x]=cc;
		rch=y;
	}
}
void insert(int k){
	cur=++cnt;
	c[cur].len=c[lst].len+1;
	pos[c[cur].len]=cur;
	top=lst;
	lst=cur;
	for(;top&&!now;top=c[top].link) now=cur;
	if(!top) return (void)(c[cur].link=rt);
	if(c[top].len+1==c[now].len) return (void)(c[cur].link=now);
	int p=++cnt,np=now;
	c[p]=c[np];
	c[p].len=c[top].len+1;
	c[np].link=c[cur].link=p;
	for(;top&&now==np;top=c[top].link) now=p; 
}
void build(){
	for(int i=cnt;i>=2;i--) f[i]=c[i].link;
}
int main(){
	scanf("%d%d",&n,&m);
	scanf("%s",s+1);
	rt=lst=++cnt;
	for(int i=1;i<=n;i++) insert(s[i]-'0');
	build();
	for(int i=1;i<=m;i++){
		scanf("%d%d",&L,&R);
		q[i]=(Que){L,R,i};
	}
	sort(q+1,q+m+1);
	int tot=0;
	for(int i=1;i<=m;i++){
		while(tot<q[i].r){
			tot++;
			access(pos[tot],tot);
		}
		ans[q[i].id]=query(q[i].l);
	}
	for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
} 

推荐阅读