【AT5162】[AGC037E] Reversing and Concatenating(贪心)
- 给定一个长度为\(n\)的字符串\(s\)。
- 执行\(k\)次操作,每次将\(s\)的反串\(s^R\)接到\(s\)后面,从中任意截取一个长度为\(n\)的字符串作为新的\(s\)。
- 求最终能得到的字典序最小的串。
- \(n\le5000,k\le10^9\)
最小字符贪心
字典序最小有个显然的贪心,就是让开头最小的字符尽可能多。
一种情况是原串的末尾就有\(t\)个最小的字符(\(t\ge 1\)),那么我们直接翻转原串,然后再以这\(2t\)个字符为结尾继续翻转,以此类推,最终能得到\(t\times 2^k\)个最小字符。
另一种情况是先把原串翻转一次,然后找到某段最小的字符(同样假设为\(t\)个),最终能得到\(t\times 2^{k-1}\)个最小字符。
直接暴力枚举这个位置,与已有答案比较更新即可。
代码:\(O(n^2)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 5000
using namespace std;
int n,k;char Mn='z',s[2*N+5];
char ans[N+5],res[N+5];I void Work(char* s,RI k)
{
RI p=n;W(s[p-1]==Mn) --p;RI l=n-p+1;W(l^n&&k) l=min(l<<1,n),--k;//翻转k次
RI i;for(i=1;i<=l;++i) res[i]=Mn;for(;i<=n;++i) res[i]=s[p-(i-l)];//l个最小字符,其后的n-l个字符
for(i=1;i<=n;++i) if(res[i]^ans[i]) {if(res[i]>ans[i]) return;break;}for(i=1;i<=n;++i) ans[i]=res[i];//和已有答案比较
}
int main()
{
RI i;for(scanf("%d%d%s",&n,&k,s+1),i=1;i<=n;++i) Mn=min(Mn,s[i]),ans[i]=s[2*n-i+1]=s[i];
if(s[n]==Mn) Work(s,k);if(k) for(i=1;i<=n;++i) s[i+n]==Mn&&(Work(s+i,k-1),0);return puts(ans+1),0;//直接按末尾翻转,翻转一次后枚举位置继续翻转
}
待到再迷茫时回头望,所有脚印会发出光芒