【SAM】gym103409J (2021CCPC桂林J)

【题意】

给定一个字符串 s s s Q Q Q次询问 s s s本质不同的子串中排名第 k k k的是哪个(输出对应最先出现的区间)。

其中排名的定义是长度小的先,长度相同字典序小的先。

∣ s ∣ , Q ≤ 1 0 6 |s|,Q\leq 10^6 s,Q106

【思路】

首先这个东西有个字典序的限制,众所周知,我们建出原串的后缀树(即反串SAM的parent树),然后按照边第一个字符从小到大的顺序DFS来遍历整颗树就可以按字典序遍历完所有的子串(当然是边压缩以后的)。

那这个边的第一个字符是什么呢?事实上就是子节点能代表的最长的子串的第一个字符(在我的代码里写的是 s [ r p o s [ i ] − m x [ f a [ i ] ] ] s[rpos[i]-mx[fa[i]]] s[rpos[i]mx[fa[i]]],其中 s s s是反串)。

一个显然的事实是,后缀树上每一条边可以给一个长度区间的子串数量+1(即 [ m x [ f a [ x ] ] + 1 , m x [ x ] ] [mx[fa[x]]+1,mx[x]] [mx[fa[x]]+1,mx[x]]),那么我们做一个差分,就可以求出每个长度的子串有多少种。

接下来问题转化为了求某个长度的排名第 k k k的子串是什么。

而前面那个显然的事实也可以导出,如果我们将询问离线,按询问子串长度顺序做,我们可以得到所有当前有贡献的边。假设要求长度为 x x x的第 k k k个子串那么只要求出边的DFS序中第 k k k条对 x x x有贡献的边是什么就行了。这个问题可以用线段树上二分解决。

于是总的复杂度 O ( ( n + q ) log ⁡ n ) O((n+q)\log n) O((n+q)logn)

这个题我使用了十分多的vector于是MLE了,把SAM的表改成map就过了,甚至还能跑的更快?

【参考代码】

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=(a),i##end=(b);i<=i##end;i++)
#define dwn(i,a,b) for(int i=(a),i##end=(b);i>=i##end;i--)
#define pb push_back
#define read yh
#define mkp make_pair
#define ll long long
#define fi first
#define se second
#define hvie '\n'

ll yh(){
	ll ret=0;bool f=0;char c=getchar();
	while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=-1;c=getchar();}
	while(isdigit(c))ret=ret*10+c-'0',c=getchar();
	return f?-ret:ret;
}

typedef pair<int,int> pii;
typedef pair<ll,int> pli;
const int N=2e6+10,M=1e6+10;
int n,Q,cnt;
int cb[N],id[N];
char s[N];
ll sum[M];
pii ans[M];
vector<pii>G[N],vec[N],qr[M];

struct SAM
{
	int sz,las,fa[N],mx[N],rpos[N];
	map<int,int>ch[N];
	void init()
	{
		sz=las=1;
	}
	int extend(int c,int tp)
	{
		int p,q,np,nq;
		p=las;las=np=++sz;mx[np]=mx[p]+1;rpos[np]=tp;
		while(p && !ch[p].count(c)) ch[p][c]=np,p=fa[p];
		if(!p) fa[np]=1;
		else
		{
			q=ch[p][c];
			if(mx[p]+1==mx[q]) fa[np]=q;
			else
			{
				nq=++sz;mx[nq]=mx[p]+1;rpos[nq]=tp;
				ch[nq]=ch[q];
				//memcpy(ch[nq],ch[q],sizeof(ch[q]));
				fa[nq]=fa[q];fa[np]=fa[q]=nq;
				while(p && ch[p][c]==q) ch[p][c]=nq,p=fa[p];
			}
		}
		return las;
	}
	void addedge(int x,int y,int w){G[x].pb(mkp(w,y));}
	void dfs(int x)
	{
		sort(G[x].begin(),G[x].end());
		for(auto pr:G[x])
		{
			int v=pr.se;
			id[v]=++cnt;cb[cnt]=v;
			vec[mx[x]+1].pb(mkp(v,1));vec[mx[v]+1].pb(mkp(v,-1));
			dfs(v);
			rpos[x]=max(rpos[x],rpos[v]);
		}
	}
	void build()
	{
		for(int i=2;i<=sz;++i) 
		{
			//printf("%d %d %d\n",fa[i],i,rpos[i]);
			++sum[mx[fa[i]]+1],--sum[mx[i]+1];
			addedge(fa[i],i,s[rpos[i]-mx[fa[i]]]-'a');
		}
		for(int i=2;i<=n;++i) sum[i]+=sum[i-1];
		for(int i=2;i<=n;++i) sum[i]+=sum[i-1];
		dfs(1);
		for(int i=1;i<=sz;++i) vector<pii>().swap(G[i]);
		//for(int i=1;i<=n;++i) printf("%lld ",sum[i]);puts("");
	}
}S;

struct Segment
{
	#define ls (x<<1)
	#define rs (x<<1|1)
	#define mid ((l+r)>>1)
	int sum[N<<2];
	void update(int x,int l,int r,int p,int val)
	{
		sum[x]+=val;
		if(l==r) return;
		if(p<=mid) update(ls,l,mid,p,val);
		else update(rs,mid+1,r,p,val);
	}
	int query(int x,int l,int r,int k)
	{
		if(l==r) return cb[l];
		if(sum[ls]>=k) return query(ls,l,mid,k);
		else return query(rs,mid+1,r,k-sum[ls]);
	}
	#undef ls
	#undef rs
	#undef mid
}T;

void solve()
{
	//T.build(1,1,cnt);
	//printf("cnt:%d\n",cnt);
	for(int len=1;len<=n;++len)
	{
		for(auto pr:vec[len])
		{
			//printf("update:%d %d %d\n",pr.fi,id[pr.fi],pr.se);
			T.update(1,1,cnt,id[pr.fi],pr.se);
		}
		for(auto pr:qr[len])
		{
			//printf("query:%lld %d\n",pr.fi,pr.se);
			int p=T.query(1,1,cnt,pr.fi);
			ans[pr.se]=mkp(n-S.rpos[p]+1,n-S.rpos[p]+len);
		}
	}
	for(int i=1;i<=Q;++i) printf("%d %d\n",ans[i].fi,ans[i].se);
}

signed main(){
	scanf("%s",s+1);n=strlen(s+1);reverse(s+1,s+n+1);
	S.init();
	for(int i=1;i<=n;++i) S.extend(s[i]-'a',i);
	S.build();
	Q=read();
	for(int i=1;i<=Q;++i)
	{
		ll k=read();
		if(k>sum[n]) {ans[i]=mkp(-1,-1);continue;}
		int p=lower_bound(sum+1,sum+n+1,k)-sum;	
		//printf("%lld %d\n",k,p);
		qr[p].pb(mkp((int)(k-sum[p-1]),i));	
	}
	solve();
	
	return 0;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值