传送门
题意:给一个字符串SSS和正整数kkk,将SSS分成最多kkk段,每段不变或翻转,使得最后的字典序最小。
∣S∣≤5×106|S|\leq5\times10^6∣S∣≤5×106
发现不翻转可以看成拆成若干单字符分别翻转,所以先分析一下必须翻转的情况
把原串翻转记为SRS^RSR,然后我们要求的是不断剪掉SRS^RSR的后缀然后依次拼起来
这样最终串的第一段是SRS^RSR的一个后缀,所以最终串的开头一定有SRS^RSR的最小后缀,但不一定是最小后缀作为第一段,因为最小后缀可能会在前面作为非后缀出现
显然这个“最小后缀”是Lyndon分解后的最后一段,记为sss 我们希望开头的sss尽量多
那么SRS^RSR可表示为a1s+t1+a2s+t2+...+ans+tn+asa_1s+t_1+a_2s+t_2+...+a_ns+t_n+asa1s+t1+a2s+t2+...+ans+tn+as(和Lyndon分解没有关系)
首先可以一刀把asasas砍掉,然后找到a1∼ana_1\sim a_na1∼an中最大的砍下来 发现这第二段是砍掉asasas后的最小后缀,相当于是下一轮的第一段
整理一下,对SRS^RSR进行Lyndon分解并合并相等段,这个Duval的时候魔改一下就可以了
然后依次砍掉最后一段并让k−1k-1k−1
注意我们假设了必须翻转,如果我们发现有连续一段的长度为111的串,相当于这一段不翻转,只需要一步
这个流程需要砍掉两段(只是后面一段和下一步的第一段重合了),所以需要k>2k>2k>2
完了之后有k≤2k \leq 2k≤2,如果剩下的只有一段直接大力讨论掉
如果k=1k=1k=1,SSS和SRS^RSR取个min\minmin即可
如果k=2k=2k=2,相当于分两段大力讨论 注意是针对原串
- 前面后面都不翻 就是原串
- 只翻后面
我们考虑找到最优的位置
从左到右循环,设当前最优位置为cutcutcut,需要更新的位置为iii 注意cut<icut<icut<i
(橙色部分为反串,TTT指SRS^RSR)
我们希望比较两个串的大小 所以从cutcutcut开始找到第一个不同的位置比较大小
首先求出Scut∼i−1S_{cut\sim i-1}Scut∼i−1与TTT的最长公共前缀,可以先跑一个exKMP,求出SSS的cutcutcut开始的后缀与TTT的最长公共前缀后和i−cuti-cuti−cut取min\minmin
如果把蓝色部分顶满了,再加上后面的部分
即TTT从i−cuti-cuti−cut开始的后缀与TTT的最长公共前缀与n−i+1n-i+1n−i+1取min\minmin
然后讨论一下找到第一个不同的字符比较大小即可
- 翻前面,后面不管
继续从SRS^RSR的结尾截后缀,设截取的后缀为TTT
考虑分解后的最后一个Lyndon串sss,TTT一定以sss开头,也以sss结尾
根据意识流,TTT一定不会只取一个分解后的LW的一部分,也不会把两个相等的LW隔开
设TTT开始的第一段为s′s's′,所以sss是s′s's′的前缀
然后有若干个s′s's′接在后面,这些s′s's′后的第一个设为ttt
根据Lyndon分解的定义,t≤s′t \leq s't≤s′。而如果t<s′t <s't<s′,那么从ttt开始截取后缀会比TTT小,与定义矛盾
所以TTT一定是s′+s′+...+s′+s+s+...+ss'+s'+...+s'+s+s+...+ss′+s′+...+s′+s+s+...+s的形式
把上面剩下的 Lyndon分解合并相等段 的倒数第二段提出来,如果sss是它的前缀,说明倒数第二段是s′s's′,此时分类讨论翻后面两段或者只翻最后一段;如果不是说明s′s's′不存在,只能翻最后一段
第二段和反串取min\minmin接在后面
复杂度O(n)O(n)O(n)
如果用std::string
的话,要注意A=A+B
和A+=B
复杂度不同……
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <string>
#include <algorithm>
#define MAXN 10000005
using namespace std;
string s,t,ts,ans;
int pos[MAXN],len[MAXN],tot;
inline string reverse(string s)
{string t;t.resize(s.size());int n=s.size();for (int i=0;i<n;i++) t[n-i-1]=s[i];return t;
}
void Duval(const string& s)
{int n=s.size();for (int i=0;i<n;){int j=i,k=i+1;while (s[j]<=s[k]) {if (s[j]==s[k]) ++j;else j=i; ++k;}len[++tot]=k-j;while (i<=j){pos[tot]=i+k-j-1;i+=k-j;}}
}
int p[MAXN];
void Exkmp(const string& s)
{int n=s.size();int mid=0,mx=0;p[0]=n;for (int i=1;i<n;i++){if (i<=mx) p[i]=min(p[i-mid],mx-i+1);while (s[i+p[i]]==s[p[i]]) ++p[i];if (i+p[i]-1>mx) mid=i,mx=i+p[i]-1;}
}
int main()
{ios::sync_with_stdio(false);cin>>s;t=reverse(s);int k;cin>>k;if (k==1) return cout<<min(s,t),0;Duval(t);pos[0]=-1;while (k>2&&tot){if (len[tot]==1){while (tot&&len[tot]==1) ans+=t.substr(pos[tot-1]+1,pos[tot]-pos[tot-1]),--tot;--k;}else ans+=t.substr(pos[tot-1]+1,pos[tot]-pos[tot-1]),--k,--tot;}if (tot==0) return cout<<ans,0;if (tot==1){string tmp=t.substr(0,pos[1]+1);tmp=min(tmp,reverse(tmp));cout<<ans+tmp;return 0;}s=reverse(t=t.substr(0,pos[tot]+1));ts=t+"#"+s;Exkmp(ts);string tmp=min(s,t);int cut=0,n=s.size();for (int i=1;i<n;i++){int cl=min(i-cut,p[n+1+cut]);if (cut+cl==i) cl+=p[cl];cl=min(cl,n-cut);if ((cl<i-cut? s[cut+cl]:t[cut+cl-i])<t[cl]) cut=i;}tmp=min(tmp,s.substr(0,cut)+t.substr(0,n-cut));string las=t.substr(pos[tot-1]+1,len[tot]);string lass=t.substr(pos[tot-2]+1,len[tot]);int st=pos[tot-1]+1;string tt=t.substr(st,n-st);string res=t.substr(0,n-tt.size());tt+=min(res,reverse(res));tmp=min(tmp,tt);if (las==lass){st=pos[tot-2]+1;tt=t.substr(st,n-st);res=t.substr(0,n-tt.size());tt=tt+min(res,reverse(res));tmp=min(tmp,tt);}cout<<ans+tmp;return 0;
}