输入输出样例
示例 1
输入
4
输出
bbaa
示例 2
输入
100
输出
jihgfeeddccbbaa
运行限制
- 最大运行时间:1s
- 最大运行内存: 256M
思路:把这题拆成两个部分,第一个部分是确定最小长度,第二个部分是确定最小字符序
求最小长度
给出一个定义:交换的次数等于字符串的逆序对个数
例如ba,b比a大,逆序对个数是1,cba,b产生1个,a产生两个,逆序对是3。
于是很自然想到,在长度相同的情况下,逆序数越多,交换次数越多。为了保证逆序对尽可能的多,我们构造的字符串尽量是递减序列。
例如:
长度 len = 1时,我们构造的字符串要形如 a
,逆序对个数为 0,交换次数为 0。
当长度 len = 2 时,我们构造的字符串要形如 ba
,逆序对个数 1,交换次数为 1。
当长度 len=3 时,我们构造的字符串要形如 cba
,逆序对个数为 3,交换次数为 3。
⋯
当长度 len=26时,我们构造的字符串得为 zyxwvutsrqponmlkjihgfedcba
,逆序对个数为 325,交换次数为 325。
题目中的要求是V最大是10000,所以这个325是远远达不到要求的,所以势必有字符是要重复的。
那接下来我们考虑一个新的问题,在只有a,b,c的情况下,构造出一个长度为7,交换次数最多的字符串,怎么想呢?给定一个字符串,插入一个字符,逆序对增加的个数=前面比该字符小的字符数量 + 后面比该字符大的字符数量。所以呢,每次我们只要插入当前字符串中字符个数最少的字符就好了~所以我们有三种方案cccbbaa,ccbbbaa,ccbbaaa,字符序最小的话,当然是ccbbaaa了。
不过我们现在的问题是:在给定的长度为len的字符串,其最大逆序数个数是多少?为什么要求这个呢,因为当我们找到第一个长度len,其最大逆序数>=V时,我们就可以从这个长度产生字符序最小的字符串,而这个len也就是最小的长度。
那回到正题,有了上边的例子,我们明白了,一个长度为len的字符串里边,要达到最大逆序数,每个字符个数最好是平均分配的,又要字符序最小,个数多的应该是字符顺序小的。字符数量为len/26+1(向下取整)的字符有len%26个,字符数量为len/26的字符有26-len%26个。那给定len,最大的交换次数的公式如下所示:
解释一下每一项吧,拿前半部分来说,含义是抽取每一个长度为len/26+1的字母,用这个长度乘剩下的长度也就是len-(len/26+1),这样就是该字母产生的逆序对,由于每个
代码如下所示:
int get_max(int len){
return ((len - (len / 26 + 1)) * (len / 26 + 1) * (len % 26) + (26 - len % 26) * (len / 26) * (len - len / 26)) / 2;
}
确定最小字符序
在上面我们已经求出了最小的长度len,也就是第一个len使得max(len)=X>=V,那么我们肯定也可以改变字符顺序,构造出逆序对个数为X,X-1......0的字符串。
于是我们可以从字符串的第一个位置往后构造,具体为:
从小到大枚举字符ch(ch∈[a~z]),判断当前位置的字符为ch是否可以构造出长度为len,且逆序对大于等于V的字符串。
- 我们设当前位置为pos,那么1~pos-1的位置已经构造好了,那么当前选择字符ch可以新增的逆序对个数就是1~pos-1中比ch大的字符个数
- 对于pos+1~len的位置,我们总共要添加len-pos个字符,且要保证这些字符能够产生最多的逆序对。我们可以暴力点一个一个插入。若插入字符x,则产生的逆序对个数(最多)=(1~pos中大于x的字符数量)+(pos后添加的总字符数量-字符x的数量)
最后若第pos个位置放置ch可以构造出长度为len,逆序对个数大于等于V的字符串,我们就令第pos个位置的字符串为ch,继续构造,否则在pos放下一个字符ch+1,继续构造。
具体代码如下所示:
#include<bits/stdc++.h>
using namespace std;
int V , len , now , cnt[27] , sum[27];
int get_max(int len){
return ((len - (len / 26 + 1)) * (len / 26 + 1) * (len % 26) + (26 - len % 26) * (len / 26) * (len - len / 26)) / 2;
}
bool check(int x , int n){
memset(cnt , 0 , sizeof(cnt));//cnt记录后面n个位置放置的对应字符数量
int add1 = 0 , add2 = 0;
for(int j = 26 ; j >= x + 1 ; j --) add1 += sum[j];//1~pos-1里边比当前插入字符大的字符数量
sum[x] ++ ;//当前字符数量增加1
for(int L = 1 ; L <= n ; L ++){
//ma保存最大逆序对个数 L-1-cnt[j]+num
//L-1-cnt[j]是当前字符之后的比当前字符小的字符数量的最大值
//num是1~pos+L-1前的比当前放置字符字典序大的字符数量
int ma = -1 , pos = 0 , num = 0;
for(int j = 26 ; j >= 1 ; j --){
if(L - 1 - cnt[j] + num > ma){
ma = L - 1 - cnt[j] + num;
pos = j;
}
num += sum[j];
}
add2 += ma , cnt[pos] ++;
}
if(now + add1 + add2 >= V) {
now += add1;
return true;
}
else {
sum[x] -- ;
return false;
}
}
signed main()
{
string ans = "";
cin >> V;
for(int i = 1 ; ; i ++) {
if(get_max(i) >= V){
len = i;
break ;
}
}
for(int i = 1 ; i <= len ; i ++){
for(int j = 1 ; j <= 26 ; j ++){
if(check(j , len - i)){
ans += char(j + 'a' - 1);
break ;
}
}
}
cout << ans << '\n';
return 0;
}
转载自蓝桥云课中的思路~
其实其中第pos之后的每个位置选择一个字符颇有点动态规划的味道,很nb的思路很好的一题~