洛谷4482 BJWC2018 Border的四种求法

Problem

洛谷

Solution

此题有精神污染,请酌量食用

先想一个暴力,可以从这个子串所代表的节点开始,询问在有效区间中是否存在endpos,如果没有就不断往上跳。在随机字符串的数据中其实是 O ( n log ⁡ n + m log ⁡ 2 n ) O(n\log n+m\log^2 n) O(nlogn+mlog2n) 的。

然而当parent树很高的时候就会被卡,那么我们就希望能优化这个暴力。先把之前的式子化一下,在节点 x x x 上,我们查询的是最靠右且满足 i &lt; l + l e n [ x ] i&lt;l+len[x] i<l+len[x] i i i,等价于 i − l e n [ x ] &lt; l i-len[x]&lt;l ilen[x]<l。那么我们同样可以用线段树维护所有 endpos 的 i − l e n [ x ] i-len[x] ilen[x] ,只需要查询最靠右的小于 r r r 且值小于 l l l 的值即可,这个在线段树上二分同样可以做到 O ( 1 ∼ 2 log ⁡ n ) O(1\sim 2\log n) O(12logn)

把树重链剖分了,这样可以把询问分在 log ⁡ n \log n logn 条路径上。在每个节点,只考虑处理它以及它的重子树中的询问,那么在这个节点贪心地把其所有轻子树的信息加入,总复杂度为 O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n)。然后还要处理匹配的节点在它子树中的情况,用个set启发式合并维护出right集合即可。

这里用到了重链剖分,而它这种思想却有些不太一样,因此我更倾向于叫它链分治,如dsu on tree就可以称为静态链分治。

时间复杂度 O ( ( n + m ) log ⁡ 2 n ) O((n+m)\log^2 n) O((n+m)log2n)


代码又丑又长,但是我已经没有精力再纠结这种事情了_(:зゝ∠)_

建议写namespace,否则你很可能会喜提如下函数名:insert,ins,ins2,merge,merge2,query,query2

Code

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef vector<int>::iterator iter;
const int maxn=400010,maxm=7500010,INF=0x3f3f3f3f;
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;}q[maxn];
int n,m,ans[maxn],pos[maxn],ptr[maxn];
char str[maxn];
vector<int> G[maxn];
set<int> s[maxn];
namespace SAM{
	int lst,sz,len[maxn],pre[maxn],ch[maxn][26];
	void insert(int c)
	{
		int p=lst,np=++sz;len[np]=len[lst]+1;lst=np;
		for(;!ch[p][c]&&p;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[q]=pre[np]=nq;
				for(;ch[p][c]==q;p=pre[p]) ch[p][c]=nq;
			}
		}
	}
}
using SAM::len;
using SAM::insert;
namespace Tree{
	struct data{int v,nxt;}edge[maxn];
	int p,head[maxn],f[maxn],hs[maxn],sz[maxn],top[maxn];
	void insert(int u,int v){edge[++p]=(data){v,head[u]};head[u]=p;}
	void dfs1(int x)
	{
		sz[x]=1;
		for(int i=head[x];i;i=edge[i].nxt)
		  if(edge[i].v^f[x])
		  {
		  	f[edge[i].v]=x;
		  	dfs1(edge[i].v);
		  	sz[x]+=sz[edge[i].v];
		  	if(sz[edge[i].v]>sz[hs[x]]) hs[x]=edge[i].v;
		  }
	}
	void dfs2(int x,int s)
	{
		top[x]=s;
		if(hs[x]) dfs2(hs[x],s);
		for(int i=head[x];i;i=edge[i].nxt)
		  if(edge[i].v^f[x]&&edge[i].v^hs[x])
		    dfs2(edge[i].v,edge[i].v);
	}
}
using Tree::f;
using Tree::hs;
using Tree::top;
using Tree::head;
using Tree::edge;
namespace SGT{
	int tot,rt[maxn],lc[maxm],rc[maxm],mn[maxm];
	void update(int l,int r,int pos,int val,int &rt)
	{
		if(!rt) rt=++tot;
		getmin(mn[rt],val);
		if(l==r) return ;
		int m=(l+r)>>1;
		if(pos<=m) update(l,m,pos,val,lc[rt]);
		else update(m+1,r,pos,val,rc[rt]);
	}
	int query(int l,int r,int pos,int val,int rt)
	{
		if(l>pos||(r<=pos&&mn[rt]>val)) return -INF;
		if(l==r) return l;
		int m=(l+r)>>1,res=query(m+1,r,pos,val,rc[rt]);
		if(res>-INF) return res;
		return query(l,m,pos,val,lc[rt]);
	}
}
using SGT::rt;
using SGT::update;
using SGT::query;
void input()
{
	int l,r,x;
	scanf("%s",str+1);read(m);
	n=strlen(str+1);SAM::lst=SAM::sz=1;
	for(int i=1;i<=n;i++){insert(str[i]-'a');ptr[i]=SAM::lst;pos[SAM::lst]=i;}
	for(int i=SAM::sz;i;i--) Tree::insert(SAM::pre[i],i);
	Tree::dfs1(1);Tree::dfs2(1,1);
	memset(SGT::mn,0x3f,sizeof(SGT::mn));
	for(int i=1;i<=m;i++)
	{
		read(q[i].l);read(q[i].r);x=ptr[q[i].r];
		ans[i]=q[i].l-1;
		for(;x;x=f[top[x]]) G[x].push_back(i);
	}
}
void clt(int x,int ori)
{
	if(pos[x]) update(1,n,pos[x],pos[x]-len[ori],rt[ori]);
	for(int i=head[x];i;i=edge[i].nxt) clt(edge[i].v,ori);
}
void dfs(int x)
{
	if(top[x]^x) rt[x]=rt[f[x]];
	for(iter it=G[x].begin();it!=G[x].end();++it)
	  getmax(ans[*it],query(1,n,q[*it].r-1,q[*it].l-1,rt[x]));
	
	for(int i=head[x];i;i=edge[i].nxt) if(edge[i].v^hs[x]) clt(edge[i].v,x);
	if(pos[x]) update(1,n,pos[x],pos[x]-len[x],rt[x]),s[x].insert(pos[x]);
	
	for(int i=head[x],v;i;i=edge[i].nxt)
	{
		dfs(v=edge[i].v);
		if(s[x].size()<s[v].size()) swap(s[x],s[v]);
		for(set<int>::iterator itr=s[v].begin();itr!=s[v].end();++itr)
		  s[x].insert(*itr);
		s[v].clear();
	}
	for(iter it=G[x].begin();it!=G[x].end();++it)
	{
		set<int>::iterator itr=s[x].lower_bound(min(q[*it].l+len[x],q[*it].r));
		if(itr!=s[x].begin()){--itr;getmax(ans[*it],*itr);}
	}
}
int main()
{
	input();
	dfs(1);
	for(int i=1;i<=m;i++) printf("%d\n",ans[i]-q[i].l+1);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值