[雅礼集训 2017 Day7]事情的相似度

事情的相似度

题解

挺好想的一道题。

相当于要求一个区间的前缀的最长公共后缀。应该很容易就能想到后缀自动机。貌似有人用LCT做
我们可以先将SAM建出来,而两段后缀的lcs就是它们的lca的深度。
我们要求的相当于编号在一个区间中的点的lca的最大深度。
考虑启发式合并,对于点 u u u的子树集合 S u S_{u} Su与它的儿子 v v v的子树集合 S v S_{v} Sv,我们在将它们合并在一起时,可以知道在这两个集合中的点互相匹配,它们的lcs就是 l e n u len_{u} lenu
如果将这些点对一一记录下来会达到 n 2 n^2 n2级别,无疑是在寻死。
考虑那些点会真正用到,假设我们现在在为 u u u匹配点对,可能最优匹配对象的 ( v , l e n l c a ( u , v ) ) (v,len_{lca(u,v)}) (v,lenlca(u,v))明显会构成一个凸包,所以我们在每个点匹配时,都只需要匹配它两侧的点。

将所有匹配了的点对 ( u , v ) (u,v) (u,v)放在坐标系上,该点的权值为 l e n l c a ( u , v ) len_{lca(u,v)} lenlca(u,v)
注意,记录下的点必须是横坐标大于纵坐标,这样才能保证下面扫描线的正确性。
我们需要找的就是坐标系 ( l , l ) (l,l) (l,l) ( r , r ) (r,r) (r,r)两点之间构成的正方形中所覆盖到的最大点权。
这可以将所有询问离线下来后用扫描先来处理。
我们就从 1 1 1 n n n将横坐标为 i i i的点放上去,这个可以用树状数组来维护。
对于右端点在 i i i的询问,我们就在树状数组上查询后缀的最大值即可。

总时间复杂度 O ( 我 不 会 算 ) O\left(我不会算\right) O(),但还是可以过。

源码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<map>
#include<set>
using namespace std;
#define MAXN 200005
#define lowbit(x) (x&-x)
#define reg register
typedef long long LL;
typedef unsigned long long uLL;
typedef pair<int,int> pii;
const int INF=0x7f7f7f7f;
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
int n,m,b[MAXN],id[MAXN],tr[MAXN],ans[MAXN];
set<int> s[MAXN];vector<pii>G[MAXN],q[MAXN];
void insert(int x,int y){x=n-x+1;while(x<=n)tr[x]=max(tr[x],y),x+=lowbit(x);}
int query(int x){x=n-x+1;int res=0;while(x)res=max(tr[x],res),x-=lowbit(x);return res;}
struct ming{int ch[2],len,fa;};
class SAM{
	private:
		ming a[MAXN];int tot,las,t[MAXN],ord[MAXN],ist[MAXN];
	public:
		void init(){las=++tot;}
		void extend(int x,int ai){
			int p=las,np=las=++tot;a[np].len=a[p].len+1;s[np].insert(ai);
			for(;p&&!a[p].ch[x];p=a[p].fa)a[p].ch[x]=np;
			if(!p){a[np].fa=1;return ;}int q=a[p].ch[x];
			if(a[q].len==a[p].len+1){a[np].fa=q;return ;}
			int nq=++tot;a[nq]=a[q];a[nq].len=a[p].len+1;
			a[q].fa=a[np].fa=nq;for(;p&&a[p].ch[x]==q;p=a[p].fa)a[p].ch[x]=nq;
		}
		void solve(){
			for(int i=1;i<=tot;i++)t[a[i].len]++;
			for(int i=1;i<=n;i++)t[i]+=t[i-1];
			for(int i=1;i<=tot;i++)id[i]=t[a[i].len]--;
			for(int i=1;i<=tot;i++)ord[id[i]]=i,ist[i]=i;
			for(int i=tot;i>1;i--){
				int u=ord[i];if(s[ist[u]].size()>s[ist[a[u].fa]].size())swap(ist[u],ist[a[u].fa]);
				for(auto k:s[ist[u]]){
					set<int>::iterator it=s[ist[a[u].fa]].lower_bound(k);
					if(it!=s[ist[a[u].fa]].end())G[*it].push_back(make_pair(a[a[u].fa].len,k));
					if(it!=s[ist[a[u].fa]].begin())G[k].push_back(make_pair(a[a[u].fa].len,*--it));
				}
				for(auto k:s[ist[u]])s[ist[a[u].fa]].insert(k);s[ist[u]].clear();
			}
		}
}T;
signed main(){
	read(n);read(m);T.init();
	for(int i=1;i<=n;i++)scanf("%1d",&b[i]),T.extend(b[i],i);T.solve();
	for(int i=1;i<=m;i++){
		int l,r;read(l);read(r);
		q[r].push_back(make_pair(l,i));
	}
	for(int i=1;i<=n;i++){
		for(auto j:G[i])insert(j.second,j.first);
		for(auto j:q[i])ans[j.second]=query(j.first);
	}
	for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
	return 0;
}

谢谢!!!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值