CF1530E Minimax
Description
定义一个字符串 \(t\),定义前缀函数 \(q(t\)\(i\)\()\) \((0≤i≤t.length()-1)\) 表示 \(t\)\(0\) ~ \(t\)\(i\) 这个子串前缀与后缀相等的长度。
又定义 \(f(t)=\) \(max\){ \(q(t\)\(i\)\()\) } \((0≤i≤t.length()-1)\)
现在给定这个字符串 \(t\) ,要求重排字母顺序,使得 \(f(t)\) 最小。
输出字典序最小的一种方案。
Solution
这题乍一看及其难分析,其实并没用到什么算法。
重要结论:在 \(f(x)\) 最小且至少由两种不同字符构成的情况下,f(x)=0或1。
让我们 感性地理解一下!
因为这个函数只关心前缀子串的前后缀相等的长度,所以我们的目标就是尽量让前缀更小
所以肯定要先排序。
接下来把情况分类分析:
①:考虑最简单的情况:字符串仅由一种字符构成。
不难想到,直接输出答案即可。
类似于 \(aaaaaaaaaa....a\)。
②:字符串中每一种字符只出现了一次。
这个也很容易,把字符按字典序排完即可。
类似于 \(abcdefghijk....\)。
③:字符串中字典序最小的字符只出现了一次。
因为我们要将这个前缀函数值最小,且字典序也要最小
所以把这个最小的字符提到开头肯定是最好的选择。
后面的字符直接按字典序输出即可。
类似于 \(accddegjjmmz...\)。
④:字符串中字典序最小的字符出现次数没有超过总长度的一半, 但多于 \(1\) 次。
这时可以先放两个最小的,如果最小的还有,放一个次小的用来隔开,再最小的,轮流放。
如果次小的就放次次小的,以此类推。
直到最小的全部放完,把剩下的按字典序输出即可。
这样可以保证前面连续两个最小字母这个前缀后面不会再出现,从而保证 \(f(t) = 1\)。
类似于 \(aabababacdefg...\)
⑤:字符串中字典序最小的字符出现次数超过总长度的一半,但仅包含两种字符。
此时也无法构造出 \(f(t) = 0\) 了。因为肯定要出现连续的一段前后缀相同,只能尽量把相同的前后缀搞短。
则考虑 \(f(t) = 1\) 是否能成立,发现如果我们把最小的那个字符取一个出来放第一位(这样做是要保证字典序最小),后面排完另一个字符,其后再把最小的字符排完,就可以构造出 $ f(t) = 1$。
类似于\(abbbbbbbaa...\)。
⑥:字符串里字典序最小的字符出现次数超过总长度的一半,且包含三种及以上种字符。
求最小值,还是要把最小的那个字母拿一个放前面。
为了不让 \(f(t)>1\),要放一个次小的隔开。
然后把最小排完。
由于前面开头已经出现了一个类似 \(ab\) 的前缀,所以后缀一定不能出现。然而现在已经有了一个最小的,所以要找出一个次次小的隔开,再把次小的放完。
剩下的按字典序排下来就行。
类似于 \(abaaaaacbbbbdefgh...\)
Code
// by pjx Jul.
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
#include <stack>
#define REP(i, x, y) for(register int i = x; i < y; i++)
#define rep(i, x, y) for(register int i = x; i <= y; i++)
#define PER(i, x, y) for(register int i = x; i > y; i--)
#define per(i, x, y) for(register int i = x; i >= y; i--)
#define lc (k << 1)
#define rc (k << 1 | 1)
using namespace std;
const int N = 1E5 + 5;
const int WORD = 26;
int len, t, c[WORD];
string s;
int a[N];
void solve()
{
int temp = -1;
for(int i = 0; i < 26; i++)//寻找这个字符串出现1次的字符中字典序最小的
{
if(c[i] == 1)
{
temp = i;
break;
}
}
if(temp != -1)// ③:最小的数只出现了一次
{
cout << char(temp + 'a');
c[temp]--;//先把这个取出来,加入答案
for(int i = 0; i < 26; i++)//然后按字典序挨个取即可
{
for(int j = 1; j <= c[i]; j++)
{
cout << char(i + 'a');
}
}
cout << endl;
return;
}
temp = -1;
for(int i = 0; i < 26; i++)//这个字符串中字典序最小的
{
if(c[i] != 0)
{
temp = i;
break;
}
}
if(c[temp] * 2 <= len + 2)//④:如果最小的字符出现次数没过半且大于2次
{
cout << char(temp + 'a');
cout << char(temp + 'a');//先取两个最小的
c[temp] -= 2;
while(c[temp] >= 1)//如果还有,则与后面的轮流取
{
int temp2 = -1;
for(int i = temp + 1; i < 26; i++)//次小的放完了放次次小的,以此类推
{
if(c[i] != 0)
{
temp2 = i;
break;
}
}
cout << char(temp2 + 'a');
cout << char(temp + 'a');
c[temp2]--;
c[temp]--;
}
for(int i = 0; i < 26; i++)//然后按字典序挨个取即可
{
for(int j = 1; j <= c[i]; j++)
{
cout << char(i + 'a');
}
}
return;
}
int temp2 = -1;
for(int i = temp + 1; i < 26; i++)
{
if(c[i] != 0)
{
temp2 = i;
break;
}
}
if(c[temp] + c[temp2] == len)//⑤:如果这个字符串里只出现了两个字符
{
cout << char(temp + 'a');//先取出一个最小的
c[temp]--;
for(int i = 1; i <= c[temp2]; i++)//再取完次小的
{
cout << char(temp2 + 'a');
}
for(int i = 1; i <= c[temp]; i++)//把最小的取完
{
cout << char(temp + 'a');
}
return;
}
//⑥:字符串里字典序最小的字符出现次数超过总长度的一半,且包含三种及以上种字符。
temp2 = -1;
for(int i = temp + 1; i < 26; i++)//求出次小的
{
if(c[i] != 0)
{
temp2 = i;
break;
}
}
int temp3 = -1;
for(int i = temp2 + 1; i < 26; i++)//求出次次小的
{
if(c[i] != 0)
{
temp3 = i;
break;
}
}
cout << char(temp + 'a');//先取一个最小的
c[temp]--;
cout << char(temp2 + 'a');//取一个次小的
c[temp2]--;
for(int i = 1; i <= c[temp]; i++)//取完最小的
{
cout << char(temp + 'a');
}
c[temp] = 0;
cout << char(temp3 + 'a');//取一个次次小的
c[temp3]--;
for(int i = 0; i < 26; i++)//然后按字典序挨个取即可 (取完次小的也包含在其中)
{
for(int j = 1; j <= c[i]; j++)
{
cout << char(i + 'a');
}
}
return;
}
int main()
{
cin >> t;
while(t--)
{
int cnt = 0;
memset(c, 0, sizeof c);
cin >> s;
len = s.length();
for(int i = 0; i < len; i++)
{
a[i + 1] = int(s[i] - 'a');
c[int(s[i] - 'a')]++;
}
sort(a + 1, a + len + 1);
if(a[1] == a[len])//①:这个字符串仅由一个字符构成,直接输出即可。
{
cout << s << endl;
continue;
}
int f = 1;
for(int i = 1; i < len; i++){
if(a[i] == a[i + 1])
{
f = 0;
break;
}
}
if(f)//②:这个字符串每种字符至多出现一次,排完字典序后直接输出。(我后来发现这种分类多余了,可省去)
{
for(int i = 1; i <= len; i++)
{
cout << char(a[i] + 'a');
}
cout << endl;
continue;
}
solve();
cout << endl;
}
return 0;
}