zoukankan      html  css  js  c++  java
  • 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;
    }
    
    
    
  • 相关阅读:
    Linux_进程之间的通信
    Linux_控制作业(管理)
    Linux_进程管理相关命令
    Linux_进程管理的基本概述
    文本编辑_Vim&Vi
    Linux_权限管理理论概述
    Linux_用户和组管理
    Linux_ACL文件访问控制列表
    72. VUE axios 配置信息相关
    71. VUE axios 发送并发请求(多个)
  • 原文地址:https://www.cnblogs.com/pjxpjx/p/15028347.html
Copyright © 2011-2022 走看看