题目传送门

Description

现在有一个长度为 \(n\) 的字符串,将其划分为 \(k\) 段,使得这 \(k\) 段每一段的字典序最大子串中字典序最大的字符串字典序尽量小。求出这个字符串。

\(n\le 10^5,k\le 15\)

Solution1 \(\Theta(nk)\)

我们可以设 \(f_{i,j}\) 表示从右到左第 \(i\) 个字符已经划分成 \(j\) 段的最小答案。

我们可以得到转移式:

\[f_{i,j}=\min\{\max(\max\{[n\to k],i\le n\le k\},f_{k+1,j-1})\}
\]

不难看出,\(f_{k+1,j-1}\) 从右到左单调不减,\(\max\{[n\to k],i\le n\le k\}\) 从右到左单调不升,也就是说存在一个临界点使得临界点及其左边都是 \(f_{k+1,j-1}\) 转移,临界点右边都是 \(\max\{[n\to k],i\le n\le k\}\) 转移。可以想到,真正会产生贡献的只有临界点和临界点右边一个点,而且,临界点一定是随着 \(i\) 往左移一起往左移。

考虑如何同时维护 \(\max\{[n\to k],i\le n\le k\}\),可以想到即使临界点右移,以前大的还是大,小的还是小,所以我们可以在每次 \(i\) 左移的取一个较大值即可。

所以我们就可以 \(\Theta(nk)\) 维护了。

Code1 by Reanap

#include <cstdio>
#include <cstring>
#include <algorithm>
#define pii pair <int , int>
#define mp make_pair
#define fs first
#define sc second
using namespace std; const int MAXN = 1e5 + 5; int sa[MAXN] , x[MAXN] , y[MAXN] , t[MAXN] , cnt[MAXN] , n , rk[MAXN];
char s[MAXN]; void make_suffix() {
int m = 256;
for (int i = 1; i <= n; ++i) cnt[x[i] = s[i]] ++;
for (int i = 1; i <= m; ++i) cnt[i] += cnt[i - 1];
for (int i = 1; i <= n; ++i) sa[cnt[x[i]] --] = i;
for (int k = 1; k <= n; k *= 2) {
int tot = 0;
for (int i = n - k + 1; i <= n; ++i) y[++tot] = i;
for (int i = 1; i <= n; ++i) if(sa[i] > k) y[++tot] = sa[i] - k;
for (int i = 1; i <= m; ++i) cnt[i] = 0;
for (int i = 1; i <= n; ++i) cnt[x[i]] ++;
for (int i = 1; i <= m; ++i) cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; --i) sa[cnt[x[y[i]]] --] = y[i];
tot = 1;
t[sa[1]] = 1;
for (int i = 2; i <= n; ++i) {
if(x[sa[i - 1]] != x[sa[i]] || x[sa[i - 1] + k] != x[sa[i] + k]) tot ++;
t[sa[i]] = tot;
}
for (int i = 1; i <= n; ++i) x[i] = t[i];
m = tot;
if(tot >= n) break;
}
} int _min[MAXN][21] , height[MAXN] , Log[MAXN];
void get_height() {
for (int i = 1; i <= n; ++i) rk[sa[i]] = i;
int k = 1;
Log[0] = -1;
for (int i = 1; i <= n; ++i) {
if(k) k --;
int j = sa[rk[i] - 1];
while(s[i + k] == s[j + k]) k ++;
height[rk[i]] = k;
Log[i] = Log[i >> 1] + 1;
}
for (int i = 1; i <= n; ++i) _min[i][0] = height[i];
for (int j = 1; (1 << j) <= n; ++j) {
for (int i = 1; i + (1 << j) - 1 <= n; ++i) {
_min[i][j] = min(_min[i][j - 1] , _min[i + (1 << (j - 1))][j - 1]);
}
}
} int get_min(int l , int r) {
if(l > r) swap(l , r);
l ++;
int t = Log[r - l + 1];
return min(_min[l][t] , _min[r - (1 << t) + 1][t]);
} int k;
pii dp[MAXN][20]; bool Comp(int l1 , int r1 , int l2 , int r2) {
int len1 = r1 - l1 + 1 , len2 = r2 - l2 + 1;
int LCP = get_min(rk[l1] , rk[l2]);
if(l1 + LCP > r1 || l2 + LCP > r2) return len1 > len2;
return rk[l1] > rk[l2];
} int main() {
scanf("%d" , &k);
scanf("%s" , s + 1);
n = strlen(s + 1);
make_suffix();
get_height();
int cur = n;
dp[n][1] = mp(n , n);
for (int i = n - 1; i >= 1; --i) {
if(rk[cur] < rk[i]) cur = i;
dp[i][1] = mp(cur , n);
}
for (int j = 2; j <= k; ++j) {
int r = n - j + 1 , cur = 0 , cur2 = 0;
for (int i = n - j + 1; i >= 1; --i) {
if(!cur || !Comp(cur , r , i , r)) {
cur = i;
if(!cur2) cur2 = cur;
while(r > i && Comp(cur , r , dp[r + 1][j - 1].fs , dp[r + 1][j - 1].sc)) r -- , cur2 = cur;
if(cur2 != cur && !Comp(cur2 , r + 1 , cur , r + 1)) cur2 = cur;
}
pii now = dp[r + 1][j - 1];
if(!Comp(now.fs , now.sc , cur , r)) dp[i][j] = mp(cur , r);
else if(Comp(cur2 , r + 1 , now.fs , now.sc) || !dp[r + 2][j - 1].fs) dp[i][j] = now;
else dp[i][j] = mp(cur2 , r + 1);
}
}
for (int i = dp[1][k].fs; i <= dp[1][k].sc; ++i) putchar(s[i]);
return 0;
}

Solution2 \(\Theta(n\log n)\)

可以想到,我们可以二分最后答案的字典序,每次从右到左贪心地选,每次选不动了就划分。

时间复杂度显然是 \(\Theta(n\log n)\)。

Code2

#include <bits/stdc++.h>
using namespace std; #define Int register int
#define ll long long
#define MAXN 100005 template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
template <typename T,typename ... Args> inline void read (T &t,Args&... args){read (t);read (args...);}
template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');} char s[MAXN];
int n,m,K,num,len,nowl,nowr,x[MAXN],y[MAXN],c[MAXN],h[MAXN],sa[MAXN],rk[MAXN],st[MAXN][21]; int query (int l,int r){
if (l == r) return n - sa[l] + 1;
if (l > r) swap (l,r);++ l;
int k = log2 (r - l + 1);
return min (st[l][k],st[r - (1 << k) + 1][k]);
} void getwhe (ll k){
for (Int i = 1;i <= n;k -= n - sa[i] - h[i] + 1,++ i)
if (n - sa[i] - h[i] + 1 >= k){
nowl = sa[i],nowr = sa[i] + h[i] + k - 1,len = nowr - nowl + 1;
return ;
}
} bool cmp (int l,int r){//判断[l,r]是否小于等于[nowl,nowr]
int lcp = min (query (rk[l],rk[nowl]),min (len,r - l + 1));
if (lcp == r - l + 1 && lcp <= len) return 1;
if (lcp == len) return 0;
return s[l + lcp] <= s[nowl + lcp];
} bool check (){
int cnt = 0;
for (Int i = n;i >= 1;){
int j = i + 1;
while (j > 1 && cmp(j - 1,i)) -- j;
if (j > i) return 0;
cnt ++,i = j - 1;
}
return cnt <= K;
} signed main(){
read (K),scanf ("%s",s + 1),n = strlen (s + 1);
m = 26;for (Int i = 1;i <= n;++ i) x[i] = s[i] - 'a' + 1,c[x[i]] ++;
for (Int i = 1;i <= m;++ i) c[i] += c[i - 1];
for (Int i = 1;i <= n;++ i) sa[c[x[i]] --] = i;
for (Int k = 1;k <= n;k <<= 1){
num = 0;for (Int i = n - k + 1;i <= n;++ i) y[++ num] = i;
for (Int i = 1;i <= n;++ i) if (sa[i] > k) y[++ num] = sa[i] - k;
for (Int i = 1;i <= m;++ i) c[i] = 0;for (Int i = 1;i <= n;++ i) c[x[i]] ++;for (Int i = 1;i <= m;++ i) c[i] += c[i - 1];
for (Int i = n;i >= 1;-- i) sa[c[x[y[i]]] --] = y[i];swap (x,y),x[sa[1]] = num = 1;
for (Int i = 2;i <= n;++ i) num += !(y[sa[i]] == y[sa[i - 1]] && y[sa[i] + k] == y[sa[i - 1] + k]),x[sa[i]] = num;
m = num;if (m == n) break;
}
for (Int i = 1;i <= n;++ i) rk[sa[i]] = i;
for (Int i = 1,k = 0;i <= n;++ i){
if (rk[i] == 1) k = 0;
else{
if (k) -- k;
int j = sa[rk[i] - 1];
while (i + k <= n && j + k <= n && s[i + k] == s[j + k]) ++ k;
}
h[rk[i]] = k;
}
for (Int i = 1;i <= n;++ i) st[i][0] = h[i];
for (Int j = 1;(1 << j) <= n;++ j) for (Int i = 1;i + (1 << j) - 1 <= n;++ i) st[i][j] = min (st[i][j - 1],st[i + (1 << j - 1)][j - 1]);
ll l = 1,r = 0,ans;for (Int i = 1;i <= n;++ i) r += n - sa[i] - h[i] + 1;
while (l <= r){
ll mid = (l + r) >> 1;getwhe (mid);
if (check ()) ans = mid,r = mid - 1;
else l = mid + 1;
}
getwhe (ans);
for (Int i = nowl;i <= nowr;++ i) putchar (s[i]);putchar ('\n');
return 0;
}

题解「BZOJ4310」跳蚤的更多相关文章

  1. 题解 「HDU6403」卡片游戏

    link Description 桌面上摊开着一些卡牌,这是她平时很爱玩的一个游戏.如今卡牌还在,她却不在我身边.不知不觉,我翻开了卡牌,回忆起了当时一起玩卡牌的那段时间. 每张卡牌的正面与反面都各有 ...

  2. 题解 「SCOI2016」萌萌哒

    link Description 一个长度为 $ n $ 的大数,用 $ S_1S_2S_3 \ldots S_n $表示,其中 $ S_i $ 表示数的第 $ i $ 位,$ S_1 $ 是数的最高 ...

  3. 题解 「SDOI2017」硬币游戏

    题目传送门 Description 周末同学们非常无聊,有人提议,咱们扔硬币玩吧,谁扔的硬币正面次数多谁胜利. 大家纷纷觉得这个游戏非常符合同学们的特色,但只是扔硬币实在是太单调了. 同学们觉得要加强 ...

  4. 题解 「ZJOI2018」历史

    题目传送门 Description 九条可怜是一个热爱阅读的女孩子. 这段时间,她看了一本非常有趣的小说,这本小说的架空世界引起了她的兴趣. 这个世界有 \(n\) 个城市,这 \(n\) 个城市被恰 ...

  5. 题解 「BZOJ3636」教义问答手册

    题目传送门 Description 作为泉岭精神的缔造者.信奉者.捍卫者.传承者,Pear决定印制一些教义问答手册,以满足泉岭精神日益增多的信徒.Pear收集了一些有关的诗选.语录,其中部分内容摘录在 ...

  6. 题解 「BZOJ2137」submultiple

    题目传送门 题目大意 给出 \(M,k\) ,求出 \[\sum_{x|M}\sigma(x)^k \] 给出 \(P_i\),满足 \(n=\prod_{i=1}^{n}a_i^{P_i}\),其中 ...

  7. 题解 「BZOJ2178」圆的面积并

    题目传送门 题目大意 给出 \(n\) 个圆,求它们并的面积大小. \(n\le 10^3\) 思路 如果您不会自适应辛普森法,请戳这里学习 其实我们发现,如果我们设 \(f(x)\) 表示 \(x= ...

  8. 题解 - 「MLOI」小兔叽

    小兔叽 \(\texttt{Link}\) 简单题意 有 \(n\) 个小木桩排成一行,第 \(i\) 个小木桩的高度为 \(h_i\),分数为 \(c_i\). 如果一只小兔叽在第 \(i\) 个小 ...

  9. 「雅礼集训 2017 Day7」跳蚤王国的宰相(树的重心)

    题面 来源 「 雅 礼 集 训 2017 D a y 7 」 跳 蚤 王 国 的 宰 相   传 统 2000   m s 1024   M i B {\tt「雅礼集训 2017 Day7」跳蚤王国的 ...

随机推荐

  1. 前端调用后台接口下载word文档的两种方法

    1传统的ajax虽然能提交到后台,但是返回的数据被解析成json,html,text等字符串,无法响应浏览器下载.就算使用bob模拟下载,数据量大时也不方便 废话不多说:上代码(此处是Layui监听提 ...

  2. vue中的v-cloak指令

    v-cloak不需要表达式,它会在vue实例结束编译时从绑定的html元素上移除,经常和display:none;配合使用: <div id="app" v-cloak> ...

  3. 高德地图——控件的添加&删除

    控件属性 visible //bool 默认true ov=new AMap.OverView(); ov.hide(); //ov.show(); 显示/隐藏---表示控件的添加与删除 <!D ...

  4. python 实用技巧:几十行代码将照片转换成素描图、随后打包成可执行文件(源码分享)

    效果展示 原始效果图 素描效果图 相关依赖包 # 超美观的打印库 from pprint import pprint # 图像处理库 from PIL import Image # 科学计算库 imp ...

  5. Java变量命名规范

    java命名规范 所有方法.变量.类名:见名知意 类成员变量:首字母小写.驼峰原则: 例如:lastName 第一个单词首字母小写,其余首字母大写 局部变量:首字母小写.驼峰原则 类名: 首字母小写. ...

  6. Spring Boot 入门系列(二十四)多环境配置,3分钟搞定!

    之前讲过Spring Boot 的系统配置和自定义配置,实现了按照实际项目的要求配置系统的相关熟悉.但是,在实际项目开发过程中,需要面对不同的环境,例如:开发环境,测试环境,生产环境.各个环境的数据库 ...

  7. multipass指定virualbox搭建k8s集群(选择docker作为默认容器)

    目录 前言 步骤 初始化三台虚拟机 统一安装docker 修改docker镜像源 查看masterIP 安装master节点(重点设置) 查看master的token 安装worker节点 测试 部署 ...

  8. elsa-core:4.ASP.NET Core Server with Elsa Dashboard

    在本快速入门中,我们将了解一个最小的 ASP.NET Core 应用程序,该应用程序承载 Elsa Dashboard 组件并将其连接到 Elsa Server. ElsaDashboard + Do ...

  9. Appium自动化(8) - 可定位的控件属性

    如果你还想从头学起Appium,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1693896.html 前言 在前面几篇文章可以看到,一个 ...

  10. VueJS学习资料大全

    参考:http://www.worktle.com/articles/2467/ 文档&社区 Vue.js官方网站(中文) :http://cn.vuejs.org/ Vue论坛:http:/ ...