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;
}


posted @ 2021-07-27 15:21  panjx  阅读(100)  评论(0编辑  收藏  举报