#4303. 跳蚤

题意

内存限制:256 MiB
时间限制:1000 ms
很久很久以前,森林里住着一群跳蚤。一天,跳蚤国王得到了一个神秘的字符串,它想进行研究。

首先,他会把串分成不超过 k k k 个子串,然后对于每个子串 S S S,他会从 S S S 的所有子串中选择字典序最大的那一个,并在选出来的 k k k
个子串中选择字典序最大的那一个。他称其为“魔力串”。现在他想找一个最优的分法让“魔力串”字典序最小。

k ≤ 15 k \le 15 k15 ∣ S ∣ ≤ 1 0 5 |S| \le 10^5 S105

题解

利用 S A M / S A SAM/SA SAM/SA 求出本质不同串个数,进行二分
对于 m i d mid mid ,利用 S A M / S A SAM/SA SAM/SA 求出其对应的子串 s s s
因为一个串的最大字典序子串一定是其后缀(可以画个图理解一下,所以从后往前枚举,如果 [ i , l a s t ] [i,last] [i,last] 的字典序比 s s s 大,则必须在 [ i − 1 , l a s t ] [i-1,last] [i1,last] 分割, i i i 作为新的一个 l a s t last last
求字典序只需求出最长公共前缀,可以二分 h a s h hash hash 或者 S A SA SA 进行处理
我写的是 S A M SAM SAM +二分 h a s h hash hash ,效率 O ( n l o g n − n l o g 2 n ) O(nlogn-nlog^2n) O(nlognnlog2n)(我也不会分析了

#include <bits/stdc++.h>
#define K 793999
#define LL long long
#define U unsigned LL
using namespace std;
const int N=2e5+5;
int k,n,lst=1,sz=1,t[N],e[N],tp;
LL s[N],f[N];U b[N],h[N][2];
char str[N],now[N];
struct SAM{
	int link,len,nx[26];
}a[N];
void build(int x){
	int np=++sz,p=lst;
	a[np].len=a[p].len+1;
	while(p && !a[p].nx[x])
		a[p].nx[x]=np,p=a[p].link;
	if (!p) a[np].link=1;
	else{
		int q=a[p].nx[x];
		if (a[q].len==a[p].len+1)
			a[np].link=q;
		else{
			int nq=++sz;
			a[nq]=a[q];
			a[nq].len=a[p].len+1;
			a[q].link=a[np].link=nq;
			while(p && a[p].nx[x]==q)
				a[p].nx[x]=nq,p=a[p].link;
		}
	}
	lst=np;
}
void find(int u,LL v){
	v-=f[u];if (!v) return;
	for (int j,i=0;i<26;i++)
		if ((j=a[u].nx[i])){
			if (v>s[j]) v-=s[j];
			else{
				now[++tp]=i+'a',find(j,v);
				return;
			}
		}
}
U H(int l,int r,bool ty){
	return h[r][ty]-h[l-1][ty]*b[r-l+1];
}
bool cmp(int L,int R){
	int d,l=0,r=min(tp,R-L+1);
	while(l<r){
		d=(l+r+1)>>1;
		if (H(1,d,1)==H(L,L+d-1,0))
			l=d;
		else r=d-1;
	}
	if (l==min(tp,R-L+1))
		return tp>=R-L+1;
	return str[L+l]<now[l+1];
}
bool J(){
	int res=0;
	for (int l=n,r=n;l;){
		while(cmp(l,r) && l) l--;
		if (l==r) return 0;res++;r=l;
		if (res>k) return 0;
	}
	return 1;
}
int main(){
	scanf("%d%s",&k,str+1);
	b[0]=1;n=strlen(str+1);
	for (int i=1;i<=n;i++)
		build(str[i]-'a'),b[i]=b[i-1]*K,
		h[i][0]=h[i-1][0]*K+str[i];
	for (int i=1;i<=sz;i++) e[a[i].len]++,f[i]=1;
	for (int i=1;i<=sz;i++) e[i]+=e[i-1];f[1]=0;
	for (int i=1;i<=sz;i++) t[e[a[i].len]--]=i;
	for (int j,i=sz;i;i--){
		j=t[i];s[j]=f[j];
		for (int l=0;l<26;l++)
			s[j]+=s[a[j].nx[l]];
	}
	LL l=1,r=s[1],d;while(l<r){
		d=(l+r)>>1;tp=0;find(1,d);
		for (int i=1;i<=tp;i++)
			h[i][1]=h[i-1][1]*K+now[i];
		if (J()) r=d;else l=d+1;
	}
	tp=0;find(1,l);
	for (int i=1;i<=tp;i++) putchar(now[i]);putchar('\n');
	return 0; 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值