@description@

给定一个字符串 s 与正整数 k。现在你需要进行恰好一次操作:

(1)将 s 切割成最多 k 个子串。即令 s = t1 + t2 + ... + tm (1 <= m <= k)。

(2)将其中的某些(不是全部) ti 翻转,得到 t1', t2', ... tm'。

(3)重新拼合得到 s' = t1' + t2' + ... tm'。

求字典序最小化的 s'。

原题请戳我查看ouo。

@solution@

先考虑假如可以任意划分,而没有段数的限制的话该怎么做。

注意到一个不翻转的子串可以拆解成若干个长度为 1 的翻转的子串,也就是说我们可以总默认所有子串都要翻转。

考虑将原串 \(S\) 翻转得到 \(S^r\),则我们可以将操作等效地理解成将 \(S^r\) 划分成若干子串,然后从后往前取出这些串拼合得到 \(S'\)。

要使 \(S'\) 的字典序最小,不难联想到 lyndon 分解:\(S^r\) 的 lyndon 分解就是我们想要的答案(默认大家都会,不会可以百度.jpg)。

假如加上 k 的限制,注意到当 k > 2 时我们依然可以贪心地取出最末尾的 lyndon 串。

直观理解的话,大概就是这么做不会影响之后操作的合法性。

注意到长度为 1 的 lyndon 串我们可以一起取出(对应不翻转的情况),长得一样的 lyndon 串我们也可以一起取出(一起翻转和分别翻转的结果一样)。

(这里有一个处理的 trick:考虑 lyndon 分解的过程,在 lyndon 分解的时候我们就可以把长得一样的串处理出来)

接下来考虑 k <= 2 的情况。k = 1 没什么话说,主要说一下 k = 2。

分几种情况:

(1)划分线前后的串都不翻转。情况唯一。

(2)划分线前的串不翻转,划分线后的串翻转。发现得到的 \(S'\) = \(S\) 的一个前缀 + \(S^r\) 的一个前缀。

比较字典序时可以用求 lcp 的方法比较。注意到只需要求某个子串和 \(S^r\) 的 lcp,所以把 \(S^r \#S\) 拿去建 \(Z-algorithm\)(扩展 kmp)就 OK。

(3)划分线前的串翻转。此时划分线前的串是 \(S^r\) 的一个后缀 \(T\)。

我们先要让划分线前字典序最小,至少要满足 \(T\) 的所有后缀要么字典序严格大于 \(T\),要么是 \(T\) 的前缀。

考虑依然对 \(S^r\) 进行 lyndon 分解,则 \(T\) 应该是末尾几个完整的 lyndon 串拼合,不然矛盾。

经过分析,满足上述条件的情况下,\(T\) 最多包含两种不同 lyndon 串。同时为了字典序最小,假如 \(T\) 包含了某一种 lyndon 串,就应包含所有与它相同的串。

再讨论后面要不要翻转,一共 4 种情况,暴力求出来比较字典序即可。

@accepted code@

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std; const int MAXN = 5000000; void solve1(char *S) {
int lenS = strlen(S);
for(int i=0,j=lenS-1;i<j;i++,j--)
if( S[i] < S[j] ) break;
else if( S[i] > S[j] ) {
for(i=0,j=lenS-1;i<j;i++,j--)
swap(S[i], S[j]);
break;
}
puts(S);
} void lyndon(char *S, int *f, int lenS) {
int cnt = 0;
for(int i=0;i<lenS;i++) f[i] = 0;
for(int i=0;i<lenS;) {
int j = i, k = i + 1;
while( k < lenS && S[j] <= S[k] )
j = (S[j] == S[k] ? j + 1 : i), k++;
int t = k - j; cnt++;
while( i + t - 1 < k )
f[i + t - 1] = cnt, i += t;
}
} void algorithmZ(char *S, int *f, int lenS) {
f[0] = lenS; int mx = 0, ps = 0;
for(int i=1;i<lenS;i++) {
f[i] = (i <= mx ? min(mx - i + 1, f[i - ps]) : 0);
while( S[f[i]] == S[i+f[i]] ) f[i]++;
if( i + f[i] - 1 > mx ) mx = i + f[i] - 1, ps = i;
}
} char ans[MAXN + 5], T[2*MAXN + 5]; int f[2*MAXN + 5];
int cmp(int l, int r) {
int p = f[l];
if( p >= r - l + 1 ) return 0;
else return (T[l + p] < T[p] ? -1 : 1);
}// (s[l, r] == s[0, r-l] ? 0 : (s[l, r] < s[0, r-l] ? -1 : 1))
bool cmp2(char *S, char *T, int n) {
for(int i=0;i<n;i++) {
if( S[i] < T[i] ) return true;
else if( S[i] > T[i] ) return false;
}
return true;
}// S < T
void update(char *T, int lenS) {
if( cmp2(T, ans, lenS) ) for(int i=0;i<lenS;i++) ans[i] = T[i];
}
int get(int x) {
int p; for(p = x - 1; p >= 0 && f[p] == 0; p--);
return p;
} void update2(char *S, int x, int lenS) {
for(int i=0;i<x;i++) T[i] = S[x-i-1];
for(int i=x;i<lenS;i++) T[i] = S[i];
update(T, lenS);
for(int i=x,j=lenS-1;i<j;i++,j--) swap(T[i], T[j]);
update(T, lenS);
} void solve2(char *S) {
int lenS = strlen(S);
for(int i=0;i<lenS;i++) ans[i] = S[i]; for(int i=0;i<lenS;i++) T[lenS-i-1] = S[i], T[lenS+i+1] = S[i];
algorithmZ(T, f, 2*lenS+1);
int pos = 0;
for(int i=1;i<lenS-1;i++) {
int t = cmp(lenS + 1 + pos + 1, lenS + 1 + i);
if( t == 0 ) {
if( cmp(i - pos, lenS - pos - 2) == 1 ) pos = i;
}
else if( t == -1 ) pos = i;
}
for(int i=0;i<lenS;i++) T[i] = S[i];
for(int i=pos+1,j=lenS-1;i<j;i++,j--) swap(T[i], T[j]);
update(T, lenS); for(int i=0;i<lenS;i++) T[lenS-i-1] = S[i];
lyndon(T, f, lenS);
int tmp = get(lenS - 1);
while( tmp >= 0 && f[tmp] == f[lenS - 1] ) tmp = get(tmp);
update2(S, lenS - tmp - 1, lenS);
if( tmp >= 0 ) {
int p = get(tmp);
while( p >= 0 && f[p] == f[tmp] ) p = get(p);
update2(S, lenS - p - 1, lenS);
}
puts(ans);
} void print(int l, int r) {
for(int i=l;i<=r;i++)
putchar(T[i]);
} char S[MAXN + 5];
int main() {
int k; scanf("%s%d", S, &k);
if( k == 1 ) solve1(S);
else {
int lenS = strlen(S);
for(int i=0;i<lenS;i++) T[lenS-i-1] = S[i];
lyndon(T, f, lenS);
int lst = lenS - 1, len = 0, tmp = 0;
while( k >= 2 && lst >= 0 ) {
int p = get(lst);
if( !(len == 1 && lst - p == 1) && !(f[lst] == tmp) ) {
if( k == 2 ) break;
k--;
}
print(p + 1, lst);
len = lst - p, tmp = f[lst], lst = p;
}
if( lst >= 0 ) solve2(S + (lenS - lst - 1));
}
}

@details@

直觉猜想和 lyndon 分解有关,然而想不到怎么处理 k = 2 的情况。。。

感觉考察了两个冷门的线性字符串算法。。。而且两个都是初见。。。

另外,正确性的证明什么的。。。不是很擅长不过直观理解起来都对所以就没写了啦啦啦。

@codeforces - 594E@ Cutting the Line的更多相关文章

  1. [codeforces 549]G. Happy Line

    [codeforces 549]G. Happy Line 试题描述 Do you like summer? Residents of Berland do. They especially love ...

  2. Codeforces 193A. Cutting Figure

    看起来非常神,但仅仅有三种情况 -1 , 1 ,2..... A. Cutting Figure time limit per test 2 seconds memory limit per test ...

  3. codeforces 251A Points on Line(二分or单调队列)

    Description Little Petya likes points a lot. Recently his mom has presented him n points lying on th ...

  4. Codeforces 1077D Cutting Out(二分答案)

    题目链接:Cutting Out 题意:给定一个n长度的数字序列s,要求得到一个k长度的数字序列t,每次从s序列中删掉完整的序列t,求出能删次数最多的那个数字序列t. 题解:数字序列s先转换成不重复的 ...

  5. CodeForces 998B Cutting(贪心)

    https://codeforces.com/problemset/problem/998/B 简单贪心题 代码如下: #include <stdio.h> #include <st ...

  6. codeforces 431 B Shower Line【暴力】

    题意:给出五个人的编号,分别为 1 2 3 4 5,他们在排队, 最开始的时候,1和2可以交谈,3和4可以交谈 然后1走了之后,2和3交谈,4和5可以交谈 2走了之后,3和4可以交谈, 3走了之后,4 ...

  7. Codeforces 1159F Winding polygonal line(叉积)

    其实这个几何写起来还是比较方便,只用到了叉积.首先我们贪心的考虑一种情况,对于任意给定的LR串,我们起点的选择肯定是在这些点围成的凸包端点上,对于这样的起点来说,他对于L或者R都是有选择的机会,而且一 ...

  8. codeforces 1077D Cutting Out 【二分】

    题目:戳这里 题意:给n个数的数组,要求找k个数满足,这k个数在数组中出现的次数最多. 解题思路:k个数每个数出现次数都要最大化,可以想到二分下限,主要是正确的二分不好写. 附ac代码: 1 #inc ...

  9. Codeforces Round #300 A. Cutting Banner 水题

    A. Cutting Banner Time Limit: 1 Sec  Memory Limit: 256 MB 题目连接 http://codeforces.com/contest/538/pro ...

随机推荐

  1. (数据科学学习手札84)基于geopandas的空间数据分析——空间计算篇(上)

    本文示例代码.数据及文件已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 在本系列之前的文章中我们主要讨论了g ...

  2. Maven快速入门(二)手动创建maven项目hellomaven

    之前讲过Maven介绍及环境搭建,介绍了maven的作用和如何搭建maven环境.接下来就以一个helloworld的例子来说一说如何创建maven项目以及maven项目的项目结构,最后讲maven如 ...

  3. [FlashDevelop] 003.FlashDevelop + LayaFlash + Starling环境配置及实战

    1.首先我们需要下载LayaStarling框架地址:http://layabox.com/index.php?m=content&c=index&a=lists&catid= ...

  4. ES6-常用四种数组

    1.map 1.1 个人理解 映射 一个对一个 例如:[45,57,138]与[{name:'blue',level:0},{name:'zhangsan',level:99},{name:'lisi ...

  5. Rocket - debug - Example: Selecting Harts

    https://mp.weixin.qq.com/s/HjG5S9binyniG_amC3Dr5Q 介绍riscv-debug的使用实例:如何选择核心,执行Halt/Resume请求. 1. Sele ...

  6. Rocket - diplomacy - AddressAdjuster

    https://mp.weixin.qq.com/s/X0s5CWN84GEiwpNR7tiRgA 基于AddressAdjuster介绍LazyModule的实现.   参考链接:https://g ...

  7. 3. OpenCV-Python——图像梯度算法、边缘检测、图像金字塔与轮廓检测、直方图与傅里叶变换

    一.图像梯度算法 1.图像梯度-Sobel算子 dst = cv2.Sobel(src, ddepth, dx, dy, ksize) ddepth:图像的深度 dx和dy分别表示水平和竖直方向 ks ...

  8. 面试题: hashset如何保证值不会被重复的

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 众所周知,HashSet 的值是不可能被重复的,在业务上经常被用来做数据去重的操作,那么,其内部究竟是怎 ...

  9. Physic Design:Floorplan算法概览

    仅用于学习交流,转载请联系本人. 1 floorplan是什么 floorplan常被翻译成布图规划,是指在芯片级别上对模块进行布局,也就是哪个单元放在什么地方,但是单元内部的具体布局并不关心.该步骤 ...

  10. Java实现 LeetCode 781 森林中的兔子(分析题)

    781. 森林中的兔子 森林中,每个兔子都有颜色.其中一些兔子(可能是全部)告诉你还有多少其他的兔子和自己有相同的颜色.我们将这些回答放在 answers 数组里. 返回森林中兔子的最少数量. 示例: ...