Codechef KILLKTH 后缀自动机+后缀树

题意

给一个长度为n的字符串,把这个串的所有子串按字典序排序后拼到一起组成一个新串,每次询问新串的某个字符是什么。
n ≤ 200000 n\le200000 n200000,强制在线。

分析

先通过建反串的后缀自动机把原串的后缀树建出来,不难发现把子串按字典序排序等价于求出后缀树的dfs序。
那么我们只要在dfs序上二分出对应节点,然后在对应节点里面算一算就好了。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>

typedef long long LL;

const int N=200005;
const int M=400005;

int n,sz,tot,a[M],b[M],c[M],rig[M],ch[M][26],pos[M],mx[M],last,fa[M],son[M][26];
char str[N];
LL s[M];

void extend(int x,int i)
{
	int p,q,np,nq;
	p=last;last=np=++sz;mx[np]=mx[p]+1;rig[np]++;pos[np]=i;
	for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np;
	if (!p) fa[np]=1;
	else
	{
		q=ch[p][x];
		if (mx[q]==mx[p]+1) fa[np]=q;
		else
		{
			nq=++sz;mx[nq]=mx[p]+1;
			memcpy(ch[nq],ch[q],sizeof(ch[q]));
			fa[nq]=fa[q];fa[q]=fa[np]=nq;
			for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq;
		}
	}
}

void pre()
{
	for (int i=1;i<=sz;i++) b[mx[i]]++;
	for (int i=1;i<=sz;i++) b[i]+=b[i-1];
	for (int i=sz;i>=1;i--) c[b[mx[i]]--]=i;
	for (int i=sz;i>=1;i--)
	{
		int x=c[i],y=fa[c[i]];
		rig[y]+=rig[x];pos[y]=pos[x];
		son[y][str[pos[x]-mx[y]]-'a']=x;
	}
}

void dfs(int x)
{
	a[++tot]=x;
	for (int i=0;i<26;i++)
		if (son[x][i]) dfs(son[x][i]);
}

LL get_sum(int l,int r)
{
	return (LL)(l+r)*(r-l+1)/2;
}

int solve(LL k)
{
	int l=1,r=sz;
	while (l<=r)
	{
		int mid=(l+r)/2;
		if (s[mid]<k) l=mid+1;
		else r=mid-1;
	}
	k-=s[l-1];int t=a[l],h=mx[fa[t]]+1;l=h;r=mx[t];
	while (l<=r)
	{
		int mid=(l+r)/2;
		if ((LL)rig[t]*get_sum(h,mid)<k) l=mid+1;
		else r=mid-1;
	}
	k-=(LL)rig[t]*get_sum(h,l-1);
	k=(k-1)%l+1;
	return str[pos[t]-k+1];
}

int main()
{
	scanf("%s",str+1);
	n=strlen(str+1);
	std::reverse(str+1,str+n+1);
	last=sz=1;
	for (int i=1;i<=n;i++) extend(str[i]-'a',i);
	pre();dfs(1);
	for (int i=1;i<=tot;i++) s[i]=s[i-1]+(LL)get_sum(mx[fa[a[i]]]+1,mx[a[i]])*rig[a[i]];
	int q,G=0;scanf("%d",&q);
	while (q--)
	{
		int p,ans;LL m;scanf("%d%lld",&p,&m);
		LL k=(LL)p*G%m+1;
		G+=(ans=solve(k));
		putchar(ans);puts("");
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值