Description

很久很久以前,森林里住着一群跳蚤。一天,跳蚤国王得到了一个神秘的字符串,它想进行研究。
首先,他会把串分成不超过 k 个子串,然后对于每个子串 S,他会从S的所有子串中选择字典序最大的那一个,并在选出来的 k 个子串中选择字典序最大的那一个。他称其为“魔力串”。
现在他想找一个最优的分法让“魔力串”字典序最小。

Input

第一行一个整数 k。
接下来一个长度不超过 105 的字符串 S。

Output

输出一行,表示字典序最小的“魔力串”。

Sample Input

13
bcbcbacbbbbbabbacbcbacbbababaabbbaabacacbbbccaccbcaabcacbacbcabaacbccbbcbcbacccbcccbbcaacabacaaaaaba

Sample Output

cbc

HINT

S的长度<=100000

这应该是目前见过的最鬼的一道后缀数组题了。。。

最大值最小,考虑二分答案。一开始把子串的排名和第k小的子串求出来了,但是并不知道如何check;

最初的想法是从rnk[1]开始,当前的后缀如果有本质不同的子串排名>mid,就从那个>mid的点为后缀的开头重分一组。

但这样萎得稀巴烂,因为首先这样并不能保证这些子串的子串的排名<=mid,而且这样的贪心也没有正确性。

考虑从sa数组从后往前贪心,每次往前移的时候要把a[i..last]和排名为mid的子串比较一下字典序,如果大于就重分一组,比较子串的话字典序可以找这两个子串的lcp来实现;

这样为什么就保证了子串的子串的排名<=mid呢?因为以i开头的后缀,长度越长字典序越大,所以a[i..last]是以i开头的子串的字典序最大值,最大值都<=mid,其余的子串肯定也都满足。。。

用lst大佬的话来说就是一段区间中,字典序最大的子串的结尾一定是区间的末尾(和我一个意思。。。),所以可以从后往前贪心。。。

(i为当前扩展的节点,last为这个子串的最后一个元素)

最后判断分的组数是否超过k;

至于本质不同的子串的排名是经典板子,不做赘述,每次打一个新的后缀数组题就感觉以前打的一些东西是错的。。。

是不是求LCP的时候要特判(l==r) ???

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#define RG register
#define ll long long
using namespace std;
const int N=1e6+10;
struct data{
int fir,sec,id;
}x[N];
int sa[N],y[N],rnk[N],rk,height[N],len,k,lx,rx,pre[N],pre2[N],ST[N][20];
ll sum[N];
char a[N];
bool cmp(const data &a,const data &b){
if(a.fir==b.fir) return a.sec<b.sec;
else return a.fir<b.fir;
}
void work2(){
rk=1;y[x[1].id]=rk;
for(RG int i=2;i<=len;i++){
if(x[i-1].fir!=x[i].fir||x[i-1].sec!=x[i].sec) rk++;
y[x[i].id]=rk;
}
}
void work(){
sort(x+1,x+1+len,cmp);work2();
for(RG int i=1;i<=len;i<<=1){
for(RG int j=1;j+i<=len;j++) x[j].fir=y[j],x[j].sec=y[j+i],x[j].id=j;
for(RG int j=len-i+1;j<=len;j++) x[j].fir=y[j],x[j].sec=0,x[j].id=j;
sort(x+1,x+1+len,cmp);work2();
if(rk==len) break;
}
for(int i=1;i<=len;i++) sa[y[i]]=i;
}
void get_height(){
int kk=0;for(RG int i=1;i<=len;i++) rnk[sa[i]]=i;
for(RG int i=1;i<=len;i++){
if(kk) kk--;
int j=sa[rnk[i]-1];
while(a[i+kk]==a[j+kk]) kk++;
height[rnk[i]]=kk;
}
}
void make_ST(){
pre[0]=1;for(int i=1;i<=16;i++) pre[i]=pre[i-1]<<1;
pre2[0]=-1;for(int i=1;i<=len;i++) pre2[i]=pre2[i>>1]+1;
for(RG int i=2;i<=len;i++) ST[i][0]=height[i];
for(RG int j=1;j<=16;j++)
for(RG int i=2;i<=len;i++){
if(i+pre[j]-1<=len){
ST[i][j]=min(ST[i][j-1],ST[i+pre[j-1]][j-1]);
}
}
}
int query(int l,int r){
if(l>r) swap(l,r);
int x=pre2[r-l+1];
return min(ST[l][x],ST[r-pre[x]+1][x]);
}
int LCP(int l,int r){
if(l==r) return len-sa[l];
if(l>r) swap(l,r);
return query(l+1,r);
}
bool compare(int l1,int r1,int l2,int r2){
int len1=r1-l1+1,len2=r2-l2+1,lcp=LCP(rnk[l1],rnk[l2]);
lcp=min(lcp,min(len1,len2));
if(lcp!=len1&&lcp!=len2) return a[l1+lcp]<=a[l2+lcp];
if(lcp==len1) return 1;
if(lcp==len2) return 0;
}
void get_kth(ll kk){
for(RG int i=1;i<=len;i++){
if(sum[i]>=kk){
lx=sa[i];rx=sa[i]+height[i]-1+(kk-sum[i-1]);
break;
}
}
}
bool check(ll mid){
get_kth(mid);int last=len,ret=1;
for(RG int i=len;i>=1;i--){
if(!compare(i,last,lx,rx)){ret++,last=i;}
if(ret>k) return 0;
}
return 1;
}
int main(){
cin>>k;scanf("%s",a+1);len=strlen(a+1);
for(RG int i=1;i<=len;i++) x[i].id=i,x[i].fir=x[i].sec=a[i]-'a'+1;
work();get_height();
for(int i=1;i<=len;i++){sum[i]=sum[i-1]+len-sa[i]+1-height[i];}
ll L=1,R=sum[len],ans;make_ST();
while(L<=R){
ll mid=(L+R)>>1;
if(check(mid)) ans=mid,R=mid-1;
else L=mid+1;
}
get_kth(ans);
for(int i=lx;i<=rx;i++) cout<<a[i];
}

  

bzoj 4310: 跳蚤的更多相关文章

  1. ●BZOJ 4310 跳蚤

    ●赘述题目 给出一个字符串,要求分成k个子串,然后求出每个子串的字典序最大的子串(我称它为子子串),要使这k个子子串中的字典序最大的那个串(即魔力串)最小.输出该魔力串. (本题个人感觉很好,比较综合 ...

  2. bzoj 4310 跳蚤——后缀数组+二分答案+贪心

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4310 答案有单调性? 二分出来一个子串,判断的时候需要满足那些字典序比它大的子串都不出现! ...

  3. bzoj 4310 跳蚤 —— 后缀数组+二分答案+贪心

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4310 二分答案——在本质不同的子串中二分答案! 如果二分到的子串位置是 st,考虑何时必须分 ...

  4. bzoj 4310 跳蚤 二分答案+后缀数组/后缀树

    题目大意 给定\(k\)和长度\(\le10^5\)的串S 把串分成不超过\(k\)个子串,然后对于每个子串\(s\),他会从\(s\)的所有子串中选择字典序最大的那一个,并在选出来的\(k\)个子串 ...

  5. bzoj 4310: 跳蚤【后缀数组+st表+二分+贪心】

    先求一下SA 本质不同的子串个数是\( \sum n-sa[i]+1-he[i] \),按字典序二分子串,判断的时候贪心,也就是从后往前扫字符串,如果当前子串串字典序大于二分的mid子串就切一下,然后 ...

  6. 后缀数组 hash求LCP BZOJ 4310: 跳蚤

    后缀数组的题博客里没放进去过..所以挖了一题写写 充实下博客 顺便留作板子.. 一个字符串S中 内容不同的子串 有 sigma{n-sa[i]+1-h[i]}   (噢 这里的h[]就是大家熟知的he ...

  7. 跳蚤 BZOJ 4310

    跳蚤 [问题描述] 很久很久以前,森林里住着一群跳蚤.一天,跳蚤国王得到了一个神秘的字符串,它想进行研究. 首先,他会把串分成不超过 k 个子串,然后对于每个子串 S,他会从S的所有子串中选择字典序最 ...

  8. 【BZOJ 4310】跳蚤

    [链接]h在这里写链接 [题意]     给你一个字符串;     让你把它分割成最多k个部分.         然后求出每个部分的字符串里面子串的字典序最大的那一个子串.         然后在这k ...

  9. bzoj 1220 跳蚤

    Written with StackEdit. Description \(Z\)城市居住着很多只跳蚤.在\(Z\)城市周六生活频道有一个娱乐节目.一只跳蚤将被请上一个高空钢丝的正中央.钢丝很长,可以 ...

随机推荐

  1. 3255:十进制到六进制-poj

    3255:十进制到六进制 总时间限制:  1000ms 内存限制:  65536kB 描述 进制转换: 将十进制(不超过int类型表示的范围)的数转换为六进制的数. 输入 输入为第一行是组数n,后面n ...

  2. CCF考试认证模拟练习——数字排序

    #include<iostream>#include<algorithm>using namespace std;struct node //定义结构体{ int num;// ...

  3. eclipse中 web项目缺少tomcatl lib的解决办法

    1.最近在搭建的项目中,将项目导入eclipse中突然报好多错误,查看后全是丢失tomcat的lib包的错误,莫名其妙的错误. 代码中缺少的也是这样的问题 很明显,我之前的包丢了,莫名其妙的丢了. 解 ...

  4. ES 入门之一 安装ElasticSearcha

    安装ElasticSearcha 学习ES也有快一个月了,但是学习的时候一直没有总结.以前没有总结是因为感觉不会的很多,现在对ES有一点了解了.索性就从头从安装到使用ES做一个详细的总结,也分享给其他 ...

  5. Python入门 - 函数方法

    本节主讲python函数的基本用法,主要包括传递参数.匿名函数和变量作用域. 一.传递参数 python除了传递必需参数外,还可以传递默认参数,不定长参数和关键字参数. 1. 传递必需参数 def m ...

  6. GDI+ 应用,Release没有错误,Debug很多。

    问题描述: 在VC6环境中,一个界面工程利用了GDI+的一些库,Release模式下编译.运行无误,但在Debug模式下就会报告错误. xxx.cpp D:\Program Files\Microso ...

  7. 享元模式(Flyweight)

    享元模式(Flyweight) 享元模式的主要目的是实现对象的共享,即共享池,当系统中对象多的时候可以减少内存的开销,通常与工厂模式一起使用. FlyWeightFactory负责创建和管理享元单元, ...

  8. mysql分区分表

    为毛要分表和分区,,,,所有数据库的通病,文件越大,性能越低...那问题就来了.数据越多文件越大...无解?哎,所以说知道 为毛要分区了吧!那分表又是毛线?分表就是把一张表拆分成若干表,,,根据情况常 ...

  9. PL/SQL简单实现数据库的连接

    通常我们都会去选择 通过修改配置文件去实现数据库链接,方法如下:找到你orale 安装下的文件:instantclient_11_2\network\admin 修改的主要有三个地方:上面的命名随便起 ...

  10. SpringBoot集成Redis实现缓存处理(Spring AOP实现)

    第一章 需求分析 计划在Team的开源项目里加入Redis实现缓存处理,因为业务功能已经实现了一部分,通过写Redis工具类,然后引用,改动量较大,而且不可以实现解耦合,所以想到了Spring框架的A ...