题目链接:点击查看

题目大意:给出一个字符串,现在要求将其分为不大于k个连续的子串,对于每个子串求出字典序最大的子串,现在要求所有子串的最大子串的最大值最小,输出这个最大子串

题目分析:最大值最小,标准的二分条件,所以我们需要想办法二分,因为最后需要的答案是子串,我们可以用后缀数组预处理出一个前缀和,表示每个后缀贡献的本质不同子串,这样我们就可以二分子串的长度,也就是二分答案了,现在问题转换为了判定问题,也就是给出一个子串,如何在分割k-1次(分割k-1次也就是分成了k块)的情况下满足子串的子串的最大值为当前答案

其实看了晚上的题解后自己也是有了一点小理解了,直接照着做法说吧,首先对于当前二分的位置,我们首先需要找到包含当前子串的后缀pos,这样在这个位置之前的子串我们都无需去管了,因为字典序肯定比当前的答案要小,我们只需要将所有大于当前答案的子串都切割一下就好了,一开始我们无需切割,只需要记录一下需要切割的位置即可,哪些位置需要切割呢?我们从pos+1开始,沿着sa数组一直往后寻找,如果lcp(pos,i)==0的话,那么当前答案一定无解,网上都是说显然无解,我来稍微说一下为什么这样显然吧,因为我们当前枚举的答案在后缀pos中一定是一个前缀,既然当前的后缀 i 与后缀 pos 的最大公共前缀为 0 ,也就是第一个字符都不相同,加上我们又是沿着sa数组升序往后找的,这就只能说明一个问题,那就是后缀 i 的首字母就比当前枚举的答案要大了,无论如何分割,最后的最大子串一定不会小于这个首字母了,所以当前的答案必定不可能符合条件了,如此一来我们必须保证lcp(pos,i)始终大于零才行,这样我们每次在[sa[i],sa[i]+len-1]中随便选一个点切割,就能使得当前后缀中不会再出现比当前二分的答案字典序还要大的答案了,列出所有的区间后,我们对其排序,以贪心的思想将割点最小化,最后再和k-1比较就能完成check函数了

这里还有一个细节需要注意一下,我们储存的是割点的区间,这个直接来储存的话不太好转换,但是我们不妨储存每个区间的首端点,因为题目要求分成的k个区间连续,所以第一个区间的首端点一定是 0 了,无需我们储存,我们只需要储存后续的k-1个区间的首端点就好了

代码:

#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cstring>
#include<algorithm>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<cmath>
#include<sstream>
using namespace std;typedef long long LL;const int inf=0x3f3f3f3f;const int N=1e5+100;char str[N];int sa[N]; //SA数组,表示将S的n个后缀从小到大排序后把排好序的
//的后缀的开头位置顺次放入SA中
int t1[N],t2[N],c[N];int rk[N],height[N],len,k;int s[N];LL sum[N];void build_sa(int s[],int n,int m)//n为添加0后的总长
{int i,j,p,*x=t1,*y=t2;for(i=0;i<m;i++) c[i]=0;for(i=0;i<n;i++) c[x[i]=s[i]]++;for(i=1;i<m;i++) c[i]+=c[i-1];for(i=n-1;i>=0;i--) sa[--c[x[i]]]=i;for(j=1;j<=n;j<<=1) {p=0;for(i=n-j;i<n;i++) y[p++]=i;for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;for(i=0;i<m;i++) c[i]=0;for(i=0;i<n;i++) c[x[y[i]]]++;for(i=1;i<m;i++) c[i]+=c[i-1];for(i=n-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i];swap(x,y);p=1,x[sa[0]]=0;for(i=1;i<n;i++) x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+j]==y[sa[i]+j]?p-1:p++;if(p>=n) break;m=p;}
}void get_height(int s[],int n)//n为添加0后的总长
{int i,j,k=0;for(i=0;i<=n;i++)rk[sa[i]]=i;for(i=0;i<n;i++) {if(k) k--;j=sa[rk[i]-1];while(s[i+k]==s[j+k]) k++;height[rk[i]]=k;}
}void solve(int base=128)
{build_sa(s,len+1,base);get_height(s,len);
}struct Node
{int l,r;Node(int L,int R){l=L,r=R;}bool operator<(const Node& a)const{if(r==a.r)return l<a.l;return r<a.r;}
};bool check(LL mid)
{int pos=lower_bound(sum+1,sum+1+len,mid)-sum;//定位sa数组 int length=mid-sum[pos-1]+height[pos];//确定串长vector<Node>cut;//记录割点if(len-sa[pos]>length)//如果对于当前后缀中还存在比二分的答案还要大的子串,需要割掉cut.push_back(Node(sa[pos],sa[pos]+length-1));for(int i=pos+1;i<=len;i++)//从 pos+1 到 n 寻找哪些需要割掉{length=min(length,height[i]);if(!length)return false;cut.push_back(Node(sa[i],sa[i]+length-1));//添加割点所在的区间}sort(cut.begin(),cut.end());//排序int cnt=0;int p=-1;for(int i=0;i<cut.size();i++)//贪心找最少的割点{if(cut[i].l>p){p=cut[i].r;cnt++;}}return cnt<k;
}int main()
{
//  freopen("input.txt","r",stdin);
//  ios::sync_with_stdio(false);while(scanf("%d",&k)!=EOF&&k){scanf("%s",str);len=strlen(str);for(int i=0;i<len;i++)s[i]=str[i]-'a'+1;s[len]=0;solve();for(int i=1;i<=len;i++)sum[i]=sum[i-1]+len-sa[i]-height[i];LL l=1,r=sum[len],ans,pos,length;while(l<=r)//二分答案,也就是字符串{LL mid=l+r>>1;if(check(mid)){ans=mid;r=mid-1;}elsel=mid+1;}int j=lower_bound(sum+1,sum+1+len,ans)-sum;//获得第ans个子串的所属后缀pos=sa[j];//找到后缀length=ans-sum[j-1]+height[j];//计算第ans个子串的长度for(int i=pos;i<pos+length;i++)putchar(str[i]);putchar('\n');}return 0;
}

HDU - 5030 Rabbit's String(后缀数组+二分)相关推荐

  1. HDU - 5008 Boring String Problem(后缀数组+二分)

    题目链接:点击查看 题目大意:给出一个字符串,接下来给出 q 个询问,每次询问字符串中第 k 大的子串,要求输出该字串的左右端点,如果有多个答案,输出左端点最小的一个 题目分析:因为在求出后缀数组后, ...

  2. HDU - 6194 string string string(后缀数组+RMQ+容斥)

    题目链接:点击查看 题目大意:给出一个字符串和一个数字 k,问字符串中出现次数恰好等于 k 次的字串有多少个 题目分析:在跑完后缀数组后,我们可以用sa数组求解,具体做法是枚举起点,找长度为 k 的s ...

  3. HDU 6194 string string string :后缀数组+单调队列 | 后缀自动机

    题意:给出一个字符串,求出出现了恰好k次的子串的个数. 题解:恰好k次 = 至少k次 - 至少k+1次.答案转化为求至少出现k次的子串个数统计.构造好后缀数组以及很重要的Height数组之后.用一个k ...

  4. HDU 6194 string string string 后缀数组 + RMQ(线段树)

    传送门:HDU6194 题意:问给定字符串中有多少种出现k次的子串. 思路:首先想到后缀数组经典问题,求出现不少于k次的子串的最大长度,类似的这题肯定就是在height数组上搞事情啦. 将height ...

  5. HDU - 4552 怪盗基德的挑战书(后缀数组+RMQ/KMP+dp)

    题目链接:点击查看 题目大意:给出一个字符串,统计每个前缀在字符串中出现的次数之和 题目分析:可以直接先用后缀数组跑出来height,再用RMQ跑出来任意两个后缀的height,我们可以将题意转换为求 ...

  6. bzoj3277 串 (后缀数组+二分答案+ST表)

    常见操作:先把所有串都连到一起,但中间加上一个特殊的符号(不能在原串中/出现过)作为分割 由于全部的子串就等于所有后缀的所有前缀,那我们对于每一个后缀,去求一个最长的前缀,来满足这个前缀在至少K个原串 ...

  7. SPOJ 220 Relevant Phrases of Annihilation(后缀数组+二分答案)

    [题目链接] http://www.spoj.pl/problems/PHRASES/ [题目大意] 求在每个字符串中出现至少两次的最长的子串 [题解] 注意到这么几个关键点:最长,至少两次,每个字符 ...

  8. 洛谷 P4094 [HEOI2016/TJOI2016]字符串 后缀数组+二分+主席树

    题目链接 后缀数组 题目分析: sa[i] – 第i小的后缀的编号 rank[i] --编号为i的后缀排第几: height[i] – 第i和第i-1的最长lcp最长公共前缀: 1.二分答案,答案肯定 ...

  9. [BZOJ4310]跳蚤-后缀数组-二分答案

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

最新文章

  1. 找到一本适合自己的SQL Server 2008入门书
  2. 木桶排序算法_这才是你想要桶排序
  3. 基于Java的RDMA高性能通信库(五):JXIO
  4. python模拟地面网管接收数据
  5. VTK:可视化之ScaleGlyphs
  6. JasperReports项目中的应用
  7. 程序员如何掌握 React 开发的黄金法则? | 技术头条
  8. 若干tif文件转换成pdf
  9. 这个阿里网盘要下线了。。
  10. Linux磁盘管理之GPT分区,磁盘管理之MBR与GPT分区
  11. 硅烷PEG硅烷,Silane-PEG-Silane
  12. HTML字体大小的设置
  13. [日语]学习“五十音”(读音篇)
  14. 读 孙卫琴《Tomcat与Javaweb开发技术详解》
  15. mac清空废纸篓怎么恢复?
  16. 计算机存储单位和网速单位换算,数据速度计算:在线进行网速各种bps mbps kbps B/秒 KB/秒 MB/秒单位之间换算...
  17. vue3 全局注册app.config.globalProperties, 如何处理getCurrentInstance 上下文线上环境报错
  18. linux的systemctl命令介绍
  19. IC从业人员基础:计算机基础知识缩写
  20. 计算机网络技术协议的三要素,通信网络协议三要素

热门文章

  1. Spring-Cloud中的网关
  2. 数据库-关系代数的分类
  3. aop实现原理-动态代理CGLib代理
  4. 适配器模式coding
  5. 计算机游戏和传统游戏的区别是什么,电竞显示器与普通显示器有什么区别-电脑自学网...
  6. java 车站分级问题_【NOIP2013 普及组】车站分级
  7. 0R电阻的12种妙用---摘自:俸禄的小三
  8. React 和 Vue的特点
  9. EFCore笔记之异步查询
  10. 问题:linux系统经常出现断网的情况,重启之后系统恢复正常