ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

CF1530E Minimax

2021-07-27 15:34:48  阅读:171  来源: 互联网

标签:字符 CF1530E int Minimax 最小 字符串 include 字典


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


标签:字符,CF1530E,int,Minimax,最小,字符串,include,字典
来源: https://www.cnblogs.com/pjxpjx/p/15028347.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有