bzoj 4310 跳蚤 二分答案+后缀数组/后缀树
题目大意
给定\(k\)和长度\(\le10^5\)的串S
把串分成不超过\(k\)个子串,然后对于每个子串\(s\),他会从\(s\)的所有子串中选择字典序最大的那一个,并在选出来的\(k\)个子串中再选择字典序最大的那一个。他称其为“魔力串”。
输出最小的魔力串
分析
最大值最小\(\Rightarrow\)二分+判定性问题
考虑对于选出来的\(k\)个子串\(s\),\(s\)中最大子串一定是\(s\)的某个后缀
做法
我们在所有本质不同字符串中按找字典序进行二分
得到一段字符
因为\(s\)中最大子串一定是\(s\)的某个后缀
我们从后往前扫(从前往后就\(n^2\)了),不行就分多一段
记录last表示上一次分割的地方
那么扫到\(i\)时\(i-last\)就是当前\(s\)的后缀
比较一下即可\(~~\) cmp调了一个小时还好意思说即可
bool cmp(int x,int y,int l1,int l2){//s[x..x+l1-1],s[y..y+l2-1]
int tp=lcp(x,y);
if(tp<l1&&tp<l2) return s[x+tp]>s[y+tp];//在比较范围直接比较
return l1>l2; //否则直接比较长度
}
实现用后缀数组方便许多
后缀树麻烦一点
solution
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdlib>
using namespace std;
typedef long long LL;
const int M=200007;
int n,m,st,len;
char s[M];
int id[M];
int last,tot;
int ch[M][26];
int fa[M],stp[M];
int ed[M];
int dfn[M],pid[M],tdfn;
int pre[M][20],dep[M],Mx;
LL sum[M];
struct edge{int y,nxt;};
struct vec{
int g[M],te;
edge e[M];
vec(){memset(g,0,sizeof(g)); te=0;}
void clear(){memset(g,0,sizeof(g)); te=0;}
inline void push(int x,int y){e[++te].y=y;e[te].nxt=g[x];g[x]=te;}
inline int& operator () (int &x) {return g[x];}
inline edge& operator [] (int &x) {return e[x];}
}go,chr;
int newnode(int ss){
stp[++tot]=ss;
return tot;
}
int ext(int p,int q,int d){
int nq=newnode(stp[p]+1); ed[nq]=ed[q]-(stp[q]-(stp[p]+1));
fa[nq]=fa[q]; fa[q]=nq;
memcpy(ch[nq],ch[q],sizeof(ch[q]));
for(;p&&ch[p][d]==q;p=fa[p]) ch[p][d]=nq;
return nq;
}
int sam(int p,int d){
int np=ch[p][d];
if(np) return (stp[p]+1==stp[np]) ? np : ext(p,np,d);
np=newnode(stp[p]+1); ed[np]=n;
for(;p&&!ch[p][d];p=fa[p]) ch[p][d]=np;
if(!p) fa[np]=1;
else{
int q=ch[p][d];
fa[np]= (stp[p]+1==stp[q]) ? q : ext(p,q,d);
}
return np;
}
void dfs(int x){
dfn[x]=++tdfn;
pid[tdfn]=x;
sum[tdfn]=stp[x]-stp[fa[x]];
int p,y;
for(p=go(x);p;p=go[p].nxt){
y=go[p].y;
dep[y]=dep[x]+1;
pre[y][0]=x;
dfs(y);
}
}
int LCA(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
for(int t=Mx;t>=0;t--)
if(dep[pre[x][t]]>=dep[y]) x=pre[x][t];
if(x==y) return x;
for(int t=Mx;t>=0;t--)
if(pre[x][t]!=pre[y][t]) x=pre[x][t],y=pre[y][t];
return pre[x][0];
}
int find(LL num){
int l=1,r=tdfn,mid;
while(l<r){
mid=l+r>>1;
if(sum[mid]>=num) r=mid;
else l=mid+1;
}
return l;
}
void getkth(LL num){
int ps=find(num);
int p=pid[ps];
num=sum[ps]-num;
st=ed[p]-stp[p]+1;
len=stp[p]-num;
}
int lcp(int x,int y){
return stp[LCA(id[x],id[y])];
}
bool cmp(int x,int y,int l1,int l2){
int tp=lcp(x,y);
if(tp<l1&&tp<l2) return s[x+tp]>s[y+tp];
return l1>l2;
}
bool check(){
int i,lst=n,blk=0;
for(i=n;i>0;i--){
if(s[i]>s[st]) return 0;
if(cmp(i,st,lst-i+1,len)) blk++,lst=i;
}
return blk+1<=m;
}
int main(){
int i,j,p;
scanf("%d",&m);
scanf("%s",s+1);
n=strlen(s+1);
last=tot=1;
for(i=n;i>0;i--) id[i]=last=sam(last,s[i]-'a');
for(i=2;i<=tot;i++)
chr.push(s[ed[i]-(stp[i]-stp[fa[i]])+1]-'a',i);
for(i=26;i>=0;i--)
for(p=chr(i);p;p=chr[p].nxt)
go.push(fa[chr[p].y],chr[p].y);
dfs(1);
Mx=log2(tot);
for(j=1;j<=Mx;j++)
for(i=1;i<=tot;i++) pre[i][j]=pre[pre[i][j-1]][j-1];
for(i=1;i<=tdfn;i++) sum[i]+=sum[i-1];
LL l=1,r=sum[tdfn],mid;
while(l<r){
mid=l+(r-l)/2;
getkth(mid);
if(check()) r=mid;
else l=mid+1;
}
getkth(l);
for(i=st;i<=st+len-1;i++) printf("%c",s[i]); puts("");
return 0;
}
bzoj 4310 跳蚤 二分答案+后缀数组/后缀树的更多相关文章
- BZOJ 2653 middle 二分答案+可持久化线段树
题目大意:有一个序列,包含多次询问.询问区间左右端点在规定区间里移动所得到的最大中位数的值. 考虑对于每个询问,如何得到最优区间?枚举显然是超时的,只能考虑二分. 中位数的定义是在一个序列中,比中位数 ...
- 【整理】如何选取后缀数组&&后缀自动机
后缀家族已知成员 后缀树 后缀数组 后缀自动机 后缀仙人掌 后缀预言 后缀Splay ? 后缀树是后缀数 ...
- 字符串的模板 Manacher kmp ac自动机 后缀数组 后缀自动机
为何scanf("%s", str)不需要&运算 经常忘掉的字符串知识点,最好不加&,不加&最标准,指针如果像scanf里一样加&是错的,大概是未定 ...
- loj6173 Samjia和矩阵(后缀数组/后缀自动机)
题目: https://loj.ac/problem/6173 分析: 考虑枚举宽度w,然后把宽度压位集中,将它们哈希 (这是w=2的时候) 然后可以写一下string=“ac#bc” 然后就是求这个 ...
- bzoj 4310 跳蚤 —— 后缀数组+二分答案+贪心
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4310 二分答案——在本质不同的子串中二分答案! 如果二分到的子串位置是 st,考虑何时必须分 ...
- 后缀数组 hash求LCP BZOJ 4310: 跳蚤
后缀数组的题博客里没放进去过..所以挖了一题写写 充实下博客 顺便留作板子.. 一个字符串S中 内容不同的子串 有 sigma{n-sa[i]+1-h[i]} (噢 这里的h[]就是大家熟知的he ...
- BZOJ3277 串 【后缀数组】【二分答案】【主席树】
题目分析: 用"$"连接后缀数组,然后做一个主席树求区间内不同的数的个数.二分一个前缀长度再在主席树上求不同的数的个数. 代码: #include<bits/stdc++.h ...
- BZOJ 2946 [Poi2000]公共串 (二分+Hash/二分+后缀数组/后缀自动机)
求多串的最长公共字串. 法1: 二分长度+hash 传送门 法2: 二分+后缀数组 传送门 法3: 后缀自动机 拿第一个串建自动机,然后用其他串在上面匹配.每次求出SAM上每个节点的最长匹配长度后,再 ...
- BZOJ 4556(后缀数组+主席树求前驱后继+二分||后缀数组+二分+可持久化线段树)
换markdown写了.. 题意: 给你一个1e5的字符串,1e5组询问,求\([l_1,r_1]\)的所有子串与\([l_2,r_2]\)的lcp 思路: 首先可以发现答案是具有单调性的,我们考虑二 ...
随机推荐
- c语言中--typeof--关键字用法
C语言中 typeof 关键字是用来定义变量数据类型的.在linux内核源代码中广泛使用. 下面是Linux内核源代码中一个关于typeof实例: #define min(x, y) ({ \ typ ...
- 转:对比python 链接 neo4j 驱动,py2neo 和 neo4j-driver 和 neo4jrestclient
Comparing Neo4j driver, py2neo and neo4jrestclient with some basic commands using the Panama Papers ...
- 7- vue django restful framework 打造生鲜超市 -商品类别数据展示(上)
Vue+Django REST framework实战 搭建一个前后端分离的生鲜超市网站 Django rtf 完成 商品列表页 并没有将列表页的数据json 与前端的页面展示结合起来 讲解如果将dr ...
- jenkins+svn+pipeline+kubernetes部署java应用(三)
将jar包.Dockerfile.kubernetes部署yaml文件上传至svn自定义目录 一.生成流水线脚本 二.配置jenkins pipeline构建语句 三.点击构建java工程
- JavaScript日期加减
JS中的日期加减使用以下方式: varcurrentDate = new Date(); 对日期加减: date.setDate(date.getDate()+n); 对月加减: date.setMo ...
- wireshark_1.6.2 使用笔记
表示ip1是否有访问ip2,进行抓包
- FLASH、SDRAM
1.flash: 闪存,掉电之后里面的存储数据不会丢失,在嵌入式系统中用作存储Bootloader以及操作系统或者程序代码或者直接当硬盘使用(U盘).一般主要使用的FLASH有NOR flash和NA ...
- layer父页面调用子页面的方法
由于不知道如何在子页面获取到layer定义的确定按钮,于是就在子页面上定义了一个方法,然后在由父页面在点确定按钮时调用子页面所定义的这个方法,从而执行子页面方法里面的内容: 子页面代码: functi ...
- Go语言之反射(二)
反射的值对象 反射不仅可以获取值的类型信息,还可以动态地获取或者设置变量的值.Go语言中使用reflect.Value获取和设置变量的值. 使用反射值对象包装任意值 Go语言中,使用reflect.V ...
- JSP自定义tld方法标签
卧槽 我们可以通过tld文件,自定义一个方法标签,以便在页面中使用,目录通常放在WEB-INF下面的tlds文件夹: 引入方式示例,直接在jsp上引入tld标签文件: <%@ taglib pr ...