【CF594E】Cutting the Line 【贪心】【Lyndon Word】【扩展kmp】
傳送門
題意:給一個字符串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+asa1?s+t1?+a2?s+t2?+...+an?s+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; }總結
以上是生活随笔為你收集整理的【CF594E】Cutting the Line 【贪心】【Lyndon Word】【扩展kmp】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Luat蓝牙指南
- 下一篇: 【BZOJ3684】大朋友和多叉树【生成