「这是我参与2022首次更文挑战的第23天,活动详情查看:2022首次更文挑战」。
这是蓝桥杯省赛的一道题目,设置难度为中等,有一定难度。但这道题的答案有些问题,用闫氏dp法好像有几个测试点无法通过,其他答案也或多或少过不去测试点,偶尔间看到一个暴力搜索的方法,好像可以通过全部测试点,这里来全盘复现一下。
题目
思路
首先我们要明白冒泡排序的排序次数是怎么算的,实际上冒泡排序的次数就是这里字符串的最大逆序对。解释一下逆序对,比如DBAA,DB,DA,DA,BA,BA这五个是逆序对,那么DBAA共有五个逆序对,它的冒泡排序就需要五次排序。明白这个,我们开始下一步,找出指定长度的最大逆序对,我们需要V次交换,假设V是4,如果长度是3,那么最大逆序对是4,字串长度肯定不是3,那就再加一个长度,长度为4,逆序对最多且字典序最小的是DCBA,这个字串的逆序数是6,需要6次交换,下一步怎么办呢?我们已经知道了字串最小的长度,接下来引入前缀和后缀的概念。字串最小长度是4,我们从第一个字符开始,第一个字符放入前缀中,,第一个字符就是A,剩下三个字符都在后缀中,然后后缀中放入字符,找到最大逆序对,(找最大逆序对的算法暂且不讲,先看思路),第一个是A肯定没有逆序对,后三个逆序对最大就是3,所以第一个不可能是A,那么A加一,变成B,找到的最大逆序对是4,BCBA(还有许多答案,就取一个),但是这个不一定是最小字典序,我们固定住第一个字符,进入下一个字符,也是从A开始,然后发现最大逆序数是3,BABA,然后A加一变成B,发现最大逆序数是4,BBAA,固定这个字符,再进入下一个字符,从A开始,最大逆序数是4,BBAA,固定这个字符,再进入下一个字符,从A开始,BBAA,到这里就结束了,所有后缀字符全部转化成了前缀字符就算结束了。这个时候所算出的字串,首先,逆序对肯定是符合要求的,其次字典序也一定是符合要求的,我们从前往后,一个个来,从小往大取,肯定是最小的字典序。不存在意外,这个做法通过了所有测试点。
代码
如何求最大逆序对
这里要说一下,免得有人看不懂代码,怎么放字符才能得最大逆序数?比如现在是CCBAA,现在我们要放一个B,我们放哪?肯定是是放成CCBBAA,这样放才能得最大逆序数,我们放一个字符,就要让其它字符都能给我们增加逆序数,这个B加进去,CC和AA都能给我们增加逆序数,这就是最好的情况,所以我们存储后缀的时候是这样存储的,比如CCCBBA,再后缀数组中就是suf[]={1,2,3,0......};第一个位子就是A,代表的就是A,A这个字符有一个,第二个位子是B,有两个B字符,第三个位子是C,有三个C字符,依次类推。我们加一个B进去,增加的逆序数就是除了B的个数其它所有字符的总个数。这样可以快速找到指定长度的最大逆序数。
#include <iostream>
using namespace std;
int pre[300];//前缀
int suf[30];//后缀
void reset(){//清除后缀
int i=0;
while(i<26&&suf[i]!=0) {
suf[i]=0;
++i;
}
}
int renum() {//计算逆序数
int cnt=0;
for(int i=0;pre[i]!=0;++i) {
for(int j=i;pre[j]!=0;++j) {
if(pre[i]>pre[j]) {
++cnt;
}
}
}
for(int i=0;pre[i]!=0;++i) {
for(int j=25;j>=0;--j) {
if(pre[i]-'a'>j) {
cnt+=suf[j];
}
}
}
int temp=0;
for(int i=0;i<26;++i) {
cnt+=temp*suf[i];
temp+=suf[i];
}
return cnt;
}
int getRe(int c) {//获取最大逆序数
int i=0,cnt=0;
while(pre[i]!=0) {
if(pre[i]>(c+'a')) {
cnt++;
}
++i;
}
for(i=0;i<26;++i) {
if(i!=c) {
cnt+=suf[i];
}
}
return cnt;
}
void set(){//在后部序列中插入元素,保证逆序数最大
int max=0,temp=0,index=0;
for(int i=0;i<26;++i) {
suf[i]++;
if((temp=getRe(i))>max) {//找出使逆序数增得最快的字符插入
index=i;
max=temp;
}
suf[i]--;
}
suf[index]++;
}
void MaxStr(int l){//获取前缀确定且长度确定的前提下的最大逆序数字串
reset();
for(int i=0;pre[i]!=0;++i,--l);
while(l>0) {
set();
--l;
}
}
void getans(int num,int l) {
for(int i=0;i<l;++i) {
for(int j=0;j<26;++j) {
pre[i]=j+'a';
MaxStr(l);
if(renum()>=num) {
break;
}
}
}
}
int main(){
int num;
cin>>num;
int l=0;
while(renum()<num) {//获取最短字串长
++l;
MaxStr(l);
}
getans(num,l);//获得目标字串
for(int i=0;pre[i]!=0;i++){
cout<<(char)pre[i];
}
return 0;
}