跳蚤

【问题描述】

很久很久以前,森林里住着一群跳蚤。一天,跳蚤国王得到了一个神秘的字符串,它想进行研究。

首先,他会把串分成不超过 k 个子串,然后对于每个子串 S,他会从S的所有子串中选择字典序最大的那一个,并在选出来的 k 个子串中选择字典序最大的那一个。他称其为“魔力串”。

现在他想找一个最优的分法让“魔力串”字典序最小。

【输入格式】

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

【输出格式】

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

【样例输入】

13
bcbcbacbbbbbabbacbcbacbbababaabbbaabacacbbbccaccbcaabcacbacbcabaacbccbbcbcbacccbcccbbcaacabacaaaaaba

【样例输出】

cbc

【数据范围】

S的长度<=100000


题解:

首先我们通过后缀数组可以知道原串本质不同的子串(长得不一样的子串)个数为∑(n + 1 - height[i] - sa[i]) (实际上就是后缀的长度(长度即为后缀的子串个数)减去重复的个数,实例看下图)

顺便结合RMQ求个lcp(最长公共前缀)

那么在这些子串上二分

当我们二分出排名时,找出对应的子串

举个例子:

ababa

排名后缀及对应子串:

a           a                                 5 + 1 - 0 - 5 = 1

aba       a ab aba                      5 + 1 - 2 - 3 = 1

ababa   a ab aba abab ababa    5 + 1 - 3 - 1 = 2

ba         b ba                            5 + 1 - 0 - 4 = 2

baba     b ba bab baba             5 + 1 - 2 - 2 = 2

那么我们只要一个个后缀枚举过去,就能找出对应排名的字符串,这也能解释本质不同的子串个数

合法答案肯定是以串中最大字符开头的,将这个情况加入判断

然后贪心,当当前分出的段未大于枚举出的子串时,继续增添字符,不然将其作为分出的一段

判断段数与要求段数的大小,继续二分

#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<map>
using namespace std;
const int me = ;
int k;
int n;
int ansl;
int ansr; int w[me];
int x[me];
int sa[me];
int he[me];
int rank[me]; int lg[me];
int f[][me]; char s[me];
inline void Sa()
{
int m = ;
for(int i = ; i <= n; ++i) ++w[x[i] = s[i] - 'a' + ];
for(int i = ; i <= m; ++i) w[i] += w[i - ];
for(int i = n; i >= ; --i) sa[w[x[i]]--] = i;
for(int k = ; k <= n; k <<= )
{
int t = ;
for(int i = n; i >= n - k + ; --i) rank[++t] = i;
for(int i = ; i <= n; ++i)
if(sa[i] > k)
rank[++t] = sa[i] - k;
for(int i = ; i <= m; ++i) w[i] = ;
for(int i = ; i <= n; ++i) ++w[x[i]];
for(int i = ; i <= m; ++i) w[i] += w[i - ];
for(int i = n; i >= ; --i) sa[w[x[rank[i]]]--] = rank[i];
m = ;
for(int i = ; i <= n; ++i)
{
int u = sa[i], v = sa[i - ];
if (x[u] != x[v] || x[u + k] != x[v + k]) rank[u] = ++m;
else rank[u] = m;
}
if (n == m) break;
for(int i = ; i <= n; ++i) swap(x[i], rank[i]);
}
int tot = ;
for(int i = ; i <= n; ++i)
{
if(tot) --tot;
int e = sa[rank[i] - ];
while (s[e + tot] == s[i + tot]) ++tot;
he[rank[i]] = tot;
}
}
inline void Log()
{
lg[] = ;
for(int i = ; i <= n; ++i)
lg[i] = (i == (i & (-i))) ? lg[i - ] + : lg[i - ];
}
inline void Rmq()
{
for(int i = ; i <= n; ++i) f[][i] = he[i];
for(int i = ; i <= lg[n]; ++i)
for(int j = ; j <= n; ++j)
f[i][j] = min(f[i - ][j], f[i - ][j + ( << (i - ))]);
}
inline void Get(const long long &mi, int &u, int &v)
{
long long e, pre;
e = ;
for(int i = ; i <= n; i ++)
{
pre = e;
e += n + - he[i] - sa[i];
if (e >= mi)
{
u = sa[i];
v = sa[i] + mi - pre + he[i] - ;
return;
}
}
}
inline int Lcp(const int &x, const int &y)
{
if (x == y) return n - x;
int a = rank[x], b = rank[y];
if (a > b) swap(a, b);
int e = lg[b - a];
return min(f[e][a + ], f[e][b - ( << e) + ]);
}
inline bool Com(const int &l1, const int &r1, const int &l2, const int &r2)
{
int len1 = r1 - l1 + , len2 = r2 - l2 + , lcp = Lcp(l1, l2);
if (len1 <= len2 && lcp >= len1) return true;
if (len1 > len2 && lcp >= len2) return false;
if (lcp >= len1 && lcp >= len2) return len1 <= len2;
return s[l1 + lcp] <= s[l2 + lcp];
}
inline bool Check(const long long &mi, int &u, int &v)
{
Get(mi, u, v);
int num = , last = n;
for(int i = n; i >= ; --i)
{
if (s[u] < s[i]) return false;
if (!Com(i, last, u, v)) ++num, last = i;
if (num >= k) return false;
}
return true;
}
inline void Two()
{
long long l, r, mi;
int u, v;
l = ;
r = ;
for(int i = ; i <= n; ++i) r += n + - sa[i] - he[i];
while(l <= r)
{
mi = (l + r) >> ;
if(Check(mi, u, v))
{
ansl = u;
ansr = v;
r = mi - ;
}
else l = mi + ;
}
}
int main()
{
scanf("%d%s", &k, s+);
n = strlen(s+);
Sa();
Log();
Rmq();
Two();
for(int i = ansl; i <= ansr; ++i) printf("%c", s[i]);
}

跳蚤 BZOJ 4310的更多相关文章

  1. bzoj 4310: 跳蚤

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

  2. ●BZOJ 4310 跳蚤

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

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

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

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

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

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

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

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

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

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

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

  8. 【BZOJ 4310】跳蚤

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

  9. BZOJ 4310 二分+SA+RMQ

    思路: 首先求出后缀数组和height数组,这样能得到本质不同的子串数目 这里利用:本质不同的子串=∑(Len−SA[i]−height[i])=∑(Len−SA[i]−height[i])利用SA[ ...

随机推荐

  1. strong 、weak、copy 、assign 、retain 、unsafe_unretained 与autoreleasing区别和作用

    strong关键字与retain关似,用了它,引用计数自动+1,用实例更能说明一切 @property (nonatomic, strong) NSString *stringA; @property ...

  2. urllib基础-请求对象request

    简单的案例-爬取百度首页 from urllib import request ''' 爬取百度首页 ''' # 确定爬去目标 base_url = 'http://www.baidu.com' # ...

  3. python 基础之运算符

    运算符 a=10 ,b=20 运算符 描述 实例 + 加 - 两个对象相加 a + b 输出结果 30 - 减 - 得到负数或是一个数减去另一个数 a - b 输出结果 -10 * 乘 - 两个数相乘 ...

  4. window10系统安装Ubuntu18.04系统

    写这篇博客整理一下使用虚拟机安装Ubuntu系统,一般常用的虚拟机有VMware以及VirtualBox.鉴于方便,博主用的是virtualbox,虽然不是很美观,但简洁,且完全免费,且不需要在自己配 ...

  5. leetcode_day1

    1.给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标. 你可以假设每种输入只会对应一个答案.但是,你不能重复利用这个数组中同样 ...

  6. Flask——蓝图

    蓝图介绍 一个项目中,有不同的模块,但是只有一个入口,程序入口可以随便取名,一般叫做,app.py或者manage.py.当我们写一个程序,当然可以在一个文件中写完,但是有一定规模的项目,我们肯定不会 ...

  7. OS复习

    提纲 一 操作系统的定义,各章节名词定义. 分时多道- OS四大特征,五大功能. 二 进程 创建终止挂起激活 PCB 原语:创建终止挂起激活唤醒 互斥和同步,临界资源,临界区 信号量的基础概念,受保护 ...

  8. Django模型中字段属性choice的使用

    根据Django官方文档: from django.db import models class Student(models.Model): FRESHMAN = 'FR' SOPHOMORE = ...

  9. vs实用插件

    Live Share 强烈推荐的一款插件,能在VS程序中打开文件并且显示他的效果.非常非常实用!,具体功能介绍在你搜索该插件时候有说明,非常非常好用的一款插件! 后续插件推荐转载参考与其他博主 1.C ...

  10. android 之 View

    在进行游戏开发时,需要自定义各种控件和界面. 自定义View的使用: 绘制屏幕 刷新屏幕:后台数据发生了变化,需要开发人员自己刷新屏幕以显示最新数据 例子: MyView开发,绘制界面View内容: ...