●赘述题目

给出一个字符串,要求分成k个子串,然后求出每个子串的字典序最大的子串(我称它为子子串),要使这k个子子串中的字典序最大的那个串(即魔力串)最小。输出该魔力串。

(本题个人感觉很好,比较综合。属于后缀数组中等题。)

●题解

方法:后缀数组+RMQ+二分

既然是要“最大的最小”,那很“习惯”地就会想到二分,大体如下

l = ;
r = n; //所有的不同的子串的个数
while ( l<=r ) {
mid =( l + r ) / ;
if ( check ( mid ) ) //如果将第mid大的子串作为魔力串,检查是否合法
ans = mid , r =mid - ; //如果合法,记录答案
else l = mid + ;
}

那么,就面临着几个问题:

1.上述代码中 n 的值多少?(即如何求不同的子串的个数):

一个串中不同子串的总数=∑(len-height[i]-sa[i])”,用这个式子就可以解决,至于为什么,自己去模拟一下吧。

2.如何找出第mid大的子串? 我相信如果弄懂了上面那个式子,应该会有想法的。

3.如何check(mid)?

给出一种思路:反向遍历原串,用一个start和end分别记录当前的开头和结尾,

若start到end这段子串的字典序大于了第mid大的子串,那么就在start和start-1之间“砍一刀”,并更新start和end,以及累计分成的段数。

若start到end这段子串的字典序小于第mid大的子串,便只更新start的值

最后,看累计的段数与输入k的大小关系,并判断返回true or false

4.在3中,如何快速比较start到end这段子串与第mid大的子串的大小关系(当然不能逐位比较):

用构建的height数组,建立ST表,用于RMQ快速求出两子串的LCP,然后就自己脑补吧。)

把以上的步骤写成函数,也就差不多完成了。

值得注意的是,对于该题的数据范围,我们求出的不同的子串的个数n应该会爆int的。无辜的我被这个错误折磨了整整5天。。。。。。

/**************************************************************
Problem: 4310
User:
Language: C++
Result: Accepted
Time:2640 ms
Memory:11656 kb
****************************************************************/
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#define ll long long
using namespace std;
char s[100006],k[100006];
int t1[100006],t2[100006],sa[100006],ra[100006],he[100006],c[100005];
int f[100006][20];
int ls,n,ke,ks,aks=-1,ake=-1;
ll l=1,r,mid;
void build_array(int m)
{
int *x=t1,*y=t2,p;
for(int i=0;i<m;i++) c[i]=0;
for(int i=0;i<ls;i++) c[x[i]=s[i]-'!'+1]++;
for(int i=1;i<m;i++) c[i]+=c[i-1];
for(int i=ls-1;i>=0;i--) sa[--c[x[i]]]=i; for(int k=1;k<=ls;k<<=1)
{
p=0;
for(int i=ls-k;i<ls;i++) y[p++]=i;
for(int i=0;i<ls;i++) if(sa[i]>=k) y[p++]=sa[i]-k; for(int i=0;i<m;i++) c[i]=0;
for(int i=0;i<ls;i++) c[x[y[i]]]++;
for(int i=1;i<m;i++) c[i]+=c[i-1];
for(int i=ls-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i]; swap(x,y);
m=2; x[sa[0]]=1;
for(int i=1;i<ls;i++)
x[sa[i]]=(y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k])?m-1:m++;
if(m>ls) break;
}
for(int i=0;i<ls;i++) ra[sa[i]]=i; p=0;
for(int i=0;i<ls;i++)
{
if(p) p--;
if(ra[i]==0) continue;
int j=sa[ra[i]-1];
while(s[i+p]==s[j+p]) p++;
he[ra[i]]=p;
}
}
void get_Kth(ll x)
{
int ng;
for(int i=0;i<ls;i++)
{
ng=ls-he[i]-sa[i];
if(ng<x) x-=ng;
else
{
ng=0;
/* for(int j=sa[i];j<=sa[i]+he[i]+x-1;j++)
{
k[ng++]=s[j];
}*/
ks=sa[i]; ke=sa[i]+he[i]+x-1;
/*k[ng]=0;*/
break;
}
}
}
void make_ST()
{
for(int i=0;i<ls;i++) f[i][0]=he[i];
for(int j=1;(1<<j)<ls;j++)
for(int i=(1<<j)&&i<ls;i<ls;i++)
f[i][j]=min(f[i][j-1],f[i-(1<<(j-1))][j-1]);
}
int rmq(int x,int y)
{
if(x>y) swap(x,y);
if(x==y) return ls-sa[x];
if(y-1==x) return f[y][0];
int j=log(y-x-1)/log(2);
return min(f[y][j],f[x+(1<<j)][j]);
}
bool check()
{
int x=ra[ks],y,end=ls-1,k=0;
for(int i=ls-1;i>=0;i--)
{
y=ra[i];
int o=rmq(x,y);
o=min(o,min(ke-ks+1,end-i+1));
if(ks+o-1>=ke&&i+o-1>=end) continue;
if(ks+o-1<ke&&i+o-1>=end) continue;
if(ks+o-1<ke&&i+o-1<end&&s[ks+o]>s[i+o]) continue;
k++;
end=i;
if(s[end]>s[ks]) return 0;
}
return k+1<=n;
}
int main()
{
scanf("%d",&n);
l=1;
scanf("%s",s);
ls=strlen(s);
build_array(300);
make_ST();
for(int i=0;i<ls;i++) r+=ls-he[i]-sa[i];
while(l<=r)
{
mid=(l+r)/2;
get_Kth(mid);
//puts(k);
if(check()) aks=ks,ake=ke,r=mid-1;
else l=mid+1;
}
if(!(n>0)) get_Kth(mid),aks=ks,ake=ke;
for(int i=aks;i<=ake;i++) putchar(s[i]);
puts("");
return 0;
}

但程序的效率和排行上的差距不小,希望有好心人能告诉我优化的方法。

●BZOJ 4310 跳蚤的更多相关文章

  1. bzoj 4310: 跳蚤

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

  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. 简单的C语言编译器--语法分析器

      语法分析算是最难的一部分了.总而言之,语法分析就是先设计一系列语法,然后再用设计好的语法去归约词法分析中的结果.最后将归约过程打印出来,或者生成抽象语法树. 1. 设计文法 以下是我的文法(引入的 ...

  2. Tornado 协程

    同步异步I/O客户端 from tornado.httpclient import HTTPClient,AsyncHTTPClient def ssync_visit(): http_client ...

  3. iOS Storyboard unwind segues使用小结

    使用storyboard开发的时候,经常会在一个scene上添加一个button,再拖拽这个button到某个想要关联的页面,最后选择push的方式跳转.这样scene_A和scene_B就有了一个& ...

  4. Java语言基础组成

    写完才发现,这个博客不提供目录这个功能,真是想骂爹了...... 目录 关键字 标识符 注释 常量和变量 运算符 语句 函数 数组 1.关键字 描述:刚刚开始学这个的时候,真是傻傻分不清楚,不过没关系 ...

  5. JAVA_SE基础——4.path的临时配置&Classpath的配置

    这次,我来写下关于path的临时配置的心的 我来说个有可能的实例:如果你去到别人的电脑 又想写代码 又不想改乱别人的path配置的话  再说别人愿意你在别人的电脑上瞎配吗? 那该怎么办呢? 那没问题 ...

  6. 用anaconda安装最新的TensorFlow版本

    Google发布了TensorFlow1.4正式版 在anaconad搜索依旧是1.2的版本,通过一番查阅,找到了方法 1,打开anaconda-prompt 2,激活你要安装的环境 activate ...

  7. mysql常用命令整理

    #不压缩备份 mysqldump -u root -p userpassword databasename > /tmp/backupfile.sql #压缩备份 mysqldump -u ro ...

  8. c 存储类型

    1,c语言中的存储类型(定义变量和函数的可见范围和生命周期)这些说明符放置在它们所修饰的类型之前.下面列出 C 程序中可用的存储类: auto register static extern 2,aut ...

  9. angular4学习笔记整理(二)angular4的路由使用

    这章说一下angular的路由 先说angular路由怎么引入,一开始new出来的angular项目它路由帮你配好了,但看要看app.module.ts里面 1.首先最上面要引入路由模块 import ...

  10. 解决IE下a标签点击有虚线边框的问题

    解决IE下a标签点击有虚线边框的问题 关键词:IE去除虚线边框.IE解决a标签虚线问题 先看看IE下,a标签出现的虚线边框问题: (上面中,红线包裹的就是一个翻页的按钮,按钮实际是hml的a标签做的, ...