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

Problem

loj

Solution

月底写来凑数的文章

两个前缀的最长后缀长度就是在SAM上的len[lca],那么问题就变成了求编号在 [ L , R ] [L,R] [L,R] 内的两个节点的最深lca

我们可以考虑离线,枚举 r r r 并维护各个 l l l 的答案。从 r − 1 r-1 r1 转移到 r r r,就是要加入 r r r 点的贡献。我们可以暴力跳 r r r 的祖先,在每个节点上记录其子树内已加入的最大的编号,这样就可以知道 l l l 最大为多少时可以使这个点做贡献,用线段树暴力取max即可。

有一个简单的优化,就是最大值相同的祖先可以跳过。这个操作其实就是相当于将点到根所有不同颜色的路径提取出来,并染成同一颜色,类似于lct中的access。而由于Tarjan大神证明了access的复杂度,所以时间复杂度大概是 O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n)

Code

#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn=200010;
template <typename Tp> inline int getmin(Tp &x,Tp y){return y<x?x=y,1:0;}
template <typename Tp> inline int getmax(Tp &x,Tp y){return y>x?x=y,1:0;}
template <typename Tp> inline void read(Tp &x)
{
    x=0;int f=0;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=1,ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    if(f) x=-x;
}
struct query{
	int l,r,id;
	bool operator < (const query &b)const{return r<b.r;}
}q[maxn];
int n,m,pos[maxn],ans[maxn];
char s[maxn];
namespace SAM{
	int sz,lst,ch[maxn][2],len[maxn],pre[maxn];
	void insert(int c)
	{
		int p=lst,np=++sz;len[np]=len[p]+1;lst=np;
		for(;p&&!ch[p][c];p=pre[p]) ch[p][c]=np;
		if(!p) pre[np]=1;
		else
		{
			int q=ch[p][c];
			if(len[q]==len[p]+1) pre[np]=q;
			else
			{
				int nq=++sz;len[nq]=len[p]+1;
				memmove(ch[nq],ch[q],sizeof(ch[q]));
				pre[nq]=pre[q];pre[np]=pre[q]=nq;
				for(;ch[p][c]==q;p=pre[p]) ch[p][c]=nq;
			}
		}
	}
}
using SAM::len;
namespace SGT{
	int mx[maxn<<2];
	void update(int l,int r,int pos,int val,int rt)
	{
		getmax(mx[rt],val);
		if(l==r) return ;
		int m=(l+r)>>1;
		if(pos<=m) update(l,m,pos,val,rt<<1);
		else update(m+1,r,pos,val,rt<<1|1);
	}
	int query(int l,int r,int L,int R,int rt)
	{
		if(L<=l&&r<=R) return mx[rt];
		int m=(l+r)>>1,res=0;
		if(L<=m) getmax(res,query(l,m,L,R,rt<<1));
		if(m<R) getmax(res,query(m+1,r,L,R,rt<<1|1));
		return res;
	}
}
using SGT::update;
using SGT::query;
namespace LCT{
	int top,stk[maxn],f[maxn],ch[maxn][2],cor[maxn];
	int isroot(int x){return ch[f[x]][0]!=x&&ch[f[x]][1]!=x;}
	void pushdown(int x){cor[ch[x][0]]=cor[ch[x][1]]=cor[x];}
	void rotate(int x)
	{
		int fa=f[x],ff=f[fa],l,r;
		l=(ch[fa][1]==x);r=l^1;
		if(!isroot(fa)){if(ch[ff][0]==fa) ch[ff][0]=x;else ch[ff][1]=x;}
		f[x]=ff;f[fa]=x;f[ch[x][r]]=fa;
		ch[fa][l]=ch[x][r];ch[x][r]=fa;
	}
	void splay(int x)
	{
		stk[top=1]=x;
		for(int i=x;!isroot(i);i=f[i]) stk[++top]=f[i];
		for(int i=top;i;i--) pushdown(stk[i]);
		while(!isroot(x))
		{
			int fa=f[x],ff=f[fa];
			if(!isroot(fa))
			{
				if((ch[ff][0]==fa)^(ch[fa][0]==x)) rotate(x);
				else rotate(fa);
			}
			rotate(x);
		}
	}
	void access(int x,int c)
	{
		for(int t=0;x;t=x,x=f[x])
		{
			splay(x);
			if(cor[x])
			  update(1,n,cor[x],len[x],1);
			cor[x]=c;ch[x][1]=t;
		}
	}
}
using LCT::access;
void input()
{
	read(n);read(m);scanf("%s",s+1);
	SAM::sz=SAM::lst=1;
	for(int i=1;i<=n;i++){SAM::insert(s[i]-'0');pos[i]=SAM::lst;}
	for(int i=SAM::sz;i;i--) LCT::f[i]=SAM::pre[i];
	for(int i=1;i<=m;i++){read(q[i].l);read(q[i].r);q[i].id=i;}
	sort(q+1,q+m+1);
}
int main()
{
	input();
	for(int i=1,j=1;i<=n&&j<=m;i++)
	{
		access(pos[i],i);
		for(;j<=m&&q[j].r<=i;j++) ans[q[j].id]=query(1,n,q[j].l,i,1);
	}
	for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值