bzoj3998:[TJOI2015]弦论
传送门
解法一:后缀数组
T=0,就是求本质不同,后缀数组经典用法
T=1,二分解决
代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
void read(int &x) {
char ch; bool ok;
for(ok=0,ch=getchar(); !isdigit(ch); ch=getchar()) if(ch=='-') ok=1;
for(x=0; isdigit(ch); x=x*10+ch-'0',ch=getchar()); if(ok) x=-x;
}
#define rg register
const int maxn=5e5+10;
int n,k,num,x[maxn],a[maxn],y[maxn],sa[maxn],rk[maxn],h[maxn],opt,m='z',sum[maxn];char p[maxn];
int main()
{
scanf("%s",p+1);read(opt),read(k);n=strlen(p+1);
for(rg int i=1;i<=n;i++)a[x[i]=p[i]]++;
for(rg int i=1;i<=m;i++)a[i]+=a[i-1];
for(rg int i=n;i;i--)sa[a[x[i]]--]=i;
for(rg int k=1;k<=n;k<<=1,num=0)
{
for(rg int i=n-k+1;i<=n;i++)y[++num]=i;
for(rg int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k;
for(rg int i=1;i<=m;i++)a[i]=0;
for(rg int i=1;i<=n;i++)a[x[i]]++;
for(rg int i=1;i<=m;i++)a[i]+=a[i-1];
for(rg int i=n;i;i--)sa[a[x[y[i]]]--]=y[i];
for(rg int i=1;i<=n;i++)y[i]=x[i];
num=x[sa[1]]=1;
for(rg int i=2;i<=n;i++)
if(y[sa[i]]!=y[sa[i-1]]||y[sa[i]+k]!=y[sa[i-1]+k])x[sa[i]]=++num;
else x[sa[i]]=num;
if(num>=n)break;m=num;
}
for(rg int i=1;i<=n;i++)rk[sa[i]]=i;
for(rg int i=1,j,k=0;i<=n;h[rk[i++]]=k)
for(k=k?k-1:k,j=sa[rk[i]-1];p[j+k]==p[i+k];k++);
for(rg int i=1;i<=n;i++)sum[i]=sum[i-1]+n-sa[i]+1;
if(!opt)
{
for(rg int i=1;i<=n;i++)
if(n-sa[i]+1<k)k=k-(n-sa[i]+1)+h[i+1];
else
{
for(rg int j=sa[i];j<=sa[i]+k-1;j++)printf("%c",p[j]);printf("\n");
return 0;
}
}
else
{
int L=1,R=n;
for(int i=1;i<=n;i++)
{
int tmp=L;
for(int j='a';j<='z';j++)
{
int l=tmp,r=R;
while(l<=r)
{
int mid=l+r>>1;
if(p[sa[mid]+i-1]>j)r=mid-1;
else l=mid+1;
}
long long t=sum[r]-sum[tmp-1]-1LL*(r-tmp+1)*(i-1);
if(k<=r-tmp+1)
{
for(int j=sa[tmp];j<=sa[tmp]+i-1;j++)printf("%c",p[j]);
return 0;
}
if(t>=k)
{
L=tmp,R=r;
k-=r-tmp+1;
break;
}
tmp=r+1,k-=t;
}
if(n-sa[L]+1==i)L++;
}
}
printf("-1\n");
}
解法二:后缀自动机
后缀数组做这个题显然有些强人所难
所以我们用后缀自动机可以更简单的解决
我们知道后缀自动机可以\(O(n)\)求出每个子串的出现次数和不同字串的个数
将这两者同意一下就可以满足这个题目的要求了
至于求第\(k\)小的,直接在后缀自动机上搜索,但是爆搜是不行的,显然会TLE
考虑我们已经求出了每个子串的出现次数,我们就可以利用这个来优化搜索了,复杂度\(O(n)\)
基数排列的时候要从1开始,从0开始会有问题,需要处理一下
代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
void read(int &x){
char ch; bool ok;
for(ok=0,ch=getchar(); !isdigit(ch); ch=getchar()) if(ch=='-') ok=1;
for(x=0; isdigit(ch); x=x*10+ch-'0',ch=getchar()); if(ok) x=-x;
}
#define rg register
const int maxn=5e6+10;
int n,tot,ans,las,sum,k,g[maxn],t[maxn],id[maxn];
int flag,cnt,f[maxn],m;
char a[maxn],b[maxn];
struct sam{int len,link,ch[26];}s[maxn];
void sam_pre(){s[++tot].len=0,s[tot].link=-1,las=tot;}
void ins(int x){
int cur=++tot,p=las;s[cur].len=s[p].len+1;f[tot]=1;
while(p!=-1&&!s[p].ch[x])s[p].ch[x]=cur,p=s[p].link;
if(p==-1)s[cur].link=1;
else{
int q=s[p].ch[x];
if(s[q].len==s[p].len+1)s[cur].link=q;
else{
int now=++tot;s[now].len=s[p].len+1;
s[now].link=s[q].link;
memcpy(s[now].ch,s[q].ch,sizeof s[q].ch);
while(p!=-1&&s[p].ch[x]==q)s[p].ch[x]=now,p=s[p].link;
s[q].link=s[cur].link=now;
}
}
las=cur;
}
void solve(int x,int k){
if(k<=f[x])return ;
k-=f[x];
for(rg int i=0;i<26;i++)
if(s[x].ch[i]){
if(k>=g[s[x].ch[i]]){k-=g[s[x].ch[i]];continue;}
putchar(i+'a'),solve(s[x].ch[i],k);return ;
}
}
int main(){
scanf("%s",a+1),read(m),read(k),n=strlen(a+1),sam_pre();
for(rg int i=1;i<=n;i++)ins(a[i]-'a');
for(rg int i=1;i<=tot;i++)t[s[i].len]++;
for(rg int i=1;i<=tot;i++)t[i]+=t[i-1];
for(rg int i=1;i<=tot;i++)id[t[s[i].len]--]=i;
for(rg int i=tot;i>=1;i--)f[s[id[i]].link]+=f[id[i]];
for(rg int i=1;i<=tot;i++)(m?(g[i]=f[i]):(g[i]=f[i]=1));
g[1]=f[1]=0;
for(rg int i=tot;i>=1;i--)
for(rg int j=0;j<26;j++)
if(s[id[i]].ch[j])g[id[i]]+=g[s[id[i]].ch[j]];
if(g[1]<k)printf("-1\n");
else solve(1,k);
}