【CF594E】Cutting the Line 【贪心】【Lyndon Word】【扩展kmp】

传送门

题意:给一个字符串 S S S和正整数 k k k,将 S S S分成最多 k k k段,每段不变或翻转,使得最后的字典序最小。

∣ S ∣ ≤ 5 × 1 0 6 |S|\leq5\times10^6 S5×106

发现不翻转可以看成拆成若干单字符分别翻转,所以先分析一下必须翻转的情况

把原串翻转记为 S R S^R SR,然后我们要求的是不断剪掉 S R S^R SR的后缀然后依次拼起来

这样最终串的第一段是 S R S^R SR的一个后缀,所以最终串的开头一定有 S R S^R SR的最小后缀,但不一定是最小后缀作为第一段,因为最小后缀可能会在前面作为非后缀出现

显然这个“最小后缀”是Lyndon分解后的最后一段,记为 s s s 我们希望开头的 s s s尽量多

那么 S R S^R SR可表示为 a 1 s + t 1 + a 2 s + t 2 + . . . + a n s + t n + a s a_1s+t_1+a_2s+t_2+...+a_ns+t_n+as a1s+t1+a2s+t2+...+ans+tn+as(和Lyndon分解没有关系)

首先可以一刀把 a s as as砍掉,然后找到 a 1 ∼ a n a_1\sim a_n a1an中最大的砍下来 发现这第二段是砍掉 a s as as后的最小后缀,相当于是下一轮的第一段

整理一下,对 S R S^R SR进行Lyndon分解并合并相等段,这个Duval的时候魔改一下就可以了

然后依次砍掉最后一段并让 k − 1 k-1 k1

注意我们假设了必须翻转,如果我们发现有连续一段的长度为 1 1 1的串,相当于这一段不翻转,只需要一步

这个流程需要砍掉两段(只是后面一段和下一步的第一段重合了),所以需要 k > 2 k>2 k>2

完了之后有 k ≤ 2 k \leq 2 k2,如果剩下的只有一段直接大力讨论掉

如果 k = 1 k=1 k=1, S S S S R S^R SR取个 min ⁡ \min min即可

如果 k = 2 k=2 k=2,相当于分两段大力讨论 注意是针对原串

  1. 前面后面都不翻 就是原串
  2. 只翻后面

我们考虑找到最优的位置

从左到右循环,设当前最优位置为 c u t cut cut,需要更新的位置为 i i i 注意 c u t < i cut<i cut<i

在这里插入图片描述
(橙色部分为反串, T T T S R S^R SR)

我们希望比较两个串的大小 所以从 c u t cut cut开始找到第一个不同的位置比较大小

首先求出 S c u t ∼ i − 1 S_{cut\sim i-1} Scuti1 T T T的最长公共前缀,可以先跑一个exKMP,求出 S S S c u t cut cut开始的后缀与 T T T的最长公共前缀后和 i − c u t i-cut icut min ⁡ \min min

如果把蓝色部分顶满了,再加上后面的部分

T T T i − c u t i-cut icut开始的后缀与 T T T的最长公共前缀与 n − i + 1 n-i+1 ni+1 min ⁡ \min min

然后讨论一下找到第一个不同的字符比较大小即可

  1. 翻前面,后面不管

继续从 S R S^R SR的结尾截后缀,设截取的后缀为 T T T

考虑分解后的最后一个Lyndon串 s s s, T T T一定以 s s s开头,也以 s s s结尾

根据意识流, T T T一定不会只取一个分解后的LW的一部分,也不会把两个相等的LW隔开

T T T开始的第一段为 s ′ s' s,所以 s s s s ′ s' s的前缀

然后有若干个 s ′ s' s接在后面,这些 s ′ s' s后的第一个设为 t t t

根据Lyndon分解的定义, t ≤ s ′ t \leq s' ts。而如果 t < s ′ t <s' t<s,那么从 t t t开始截取后缀会比 T T T小,与定义矛盾

所以 T T T一定是 s ′ + s ′ + . . . + s ′ + s + s + . . . + s s'+s'+...+s'+s+s+...+s s+s+...+s+s+s+...+s的形式

把上面剩下的 Lyndon分解合并相等段 的倒数第二段提出来,如果 s s s是它的前缀,说明倒数第二段是 s ′ s' s,此时分类讨论翻后面两段或者只翻最后一段;如果不是说明 s ′ s' s不存在,只能翻最后一段

第二段和反串取 min ⁡ \min min接在后面

复杂度 O ( n ) O(n) O(n)

如果用std::string的话,要注意A=A+BA+=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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值