LINK:跳蚤
给出一个字符串 将字符串划分成不超过k个子串 每个子串对答案的贡献为字典序最大的子串。答案为字典序最大的子串。
如何划分让答案尽可能的小。
考虑二分答案。
难点在于 分成的子串的最大子串>=mid。
mid 为答案 我们可以把排名为mid的子串给求出来 这个可以二分查找 也可以直接O(n)暴力扫。
考虑得到一个串 其排名为mid 接下来是 将原串给分割 让其子串的字典序大于这个串。
考虑越长字典序越大的缘故 可以发现 在分割一个串的时候长度要由小到大。
如果从前缀开始分配 那么 字典序较大的子串就要为开头这是很不优的。而且从前缀开始分配无法很快的检查出所有的子串。
考虑从后缀开始分配 那么 字典序较大的一般在末尾。每次子串递增一个可以快速得到大小。
所以 考虑倒序分割 每次check两个子串的字典序来判断是否分割即可。
由于把字典序尽可能大的串放到了末尾 所以这个贪心是正确的。
或者说换个角度构造答案 考虑第一段 可以发现我们不知道是哪个位置结尾。
考虑最后一段 可以发现我们知道是哪结尾 那么分的次数有限 所以需要每一段尽可能的长。
从最后开始一直往前贪 能放就放,而不是从前往后是因为没有结尾 很难判断子串,同时让后面的局面更差。
前者是让前面的结果更优。综上 贪心的策略就是这样。
const int MAXN=100010;
int n,k,m,L,R;
int x[MAXN],y[MAXN],sa[MAXN],rk[MAXN],h[MAXN],c[MAXN];
int f[MAXN][20],Log[MAXN];ll sum[MAXN];
char a[MAXN];
inline void SA()
{
m=150;
rep(1,n,i)++c[x[i]=a[i]];
rep(1,m,i)c[i]+=c[i-1];
rep(1,n,i)sa[c[x[i]]--]=i;
for(int k=1;k<=n;k=k<<1)
{
int num=0;
rep(n-k+1,n,i)y[++num]=i;
rep(1,n,i)if(sa[i]>k)y[++num]=sa[i]-k;
rep(1,m,i)c[i]=0;
rep(1,n,i)++c[x[i]];
rep(1,m,i)c[i]+=c[i-1];
fep(n,1,i)sa[c[x[y[i]]]--]=y[i];
rep(1,n,i)y[i]=x[i],x[i]=0;
x[sa[1]]=num=1;
rep(2,n,i)x[sa[i]]=y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]?num:++num;
if(n==num)break;
m=num;
}
rep(1,n,i)rk[sa[i]]=i;
}
inline void get_height()
{
int k=0;
rep(1,n,i)
{
if(rk[i]==1)continue;
if(k)--k;
int j=sa[rk[i]-1];
while(a[i+k]==a[j+k])++k;
h[rk[i]]=k;
}
rep(2,n,i)
{
f[i-1][0]=h[i];
Log[i]=Log[i>>1]+1;
}
rep(1,Log[n-1],j)
rep(1,n-1-(1<<j)+1,i)
f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
inline void get_s(ll x)
{
int l=1,r=n;
while(l<r)
{
int mid=(l+r)>>1;
if(sum[mid]>=x)r=mid;
else l=mid+1;
}
x=x-sum[l-1]+h[l];
L=sa[l];R=sa[l]+x-1;
}
inline int LCP(int x,int y)
{
if(x==y)return n-x+1;
x=rk[x];y=rk[y];
if(x>y)swap(x,y);--y;
int z=Log[y-x+1];
return min(f[x][z],f[y-(1<<z)+1][z]);
}
inline int pd(int L,int R,int l,int r)//比较两个字典序
{
int lcp=LCP(L,l);
int len1=R-L+1;
int len2=r-l+1;
if(lcp>=len1&&lcp>=len2)return len1<=len2;
if(lcp>=len1)return 1;
if(lcp>=len2)return 0;
return a[L+lcp]<a[l+lcp];
}
inline int check()
{
int last=n,cnt=0;
fep(n,1,i)
{
if(a[i]>a[L])return 0;
if(!pd(i,last,L,R))last=i,++cnt;
if(cnt>k)return 0;
}
return 1;
}
int main()
{
freopen("1.in","r",stdin);
gt(k);gc(a);n=strlen(a+1);
SA();get_height();--k;
rep(1,n,i)sum[i]=n-sa[i]+1-h[i]+sum[i-1];
ll l=1,r=sum[n];
while(l<r)
{
ll mid=(l+r)>>1;
get_s(mid);
if(check())r=mid;
else l=mid+1;
}
get_s(l);
rep(L,R,i)printf("%c",a[i]);
return 0;
}
果然 暴怒A题是最爽的。