题意:
给出一个数,最长有9位,每次可以交换其中两个位置的数,也可以不动,求k次操作后能得到的最大值与最小值
思路:
首先考虑贪心做法,取最大值时每次将最大的数尽可能放在前面,取最小值时每次将最小的数尽可能放在前面,但是我们会发现对于存在很多相同数的情况,会出现错误
例如: k=2时的970979,贪心的求出最大值是999077,但实际上可以达到的最大值是999770
所以我们只能采用搜索的方式,对于本题,我们枚举所有在k次操作内能得到的情况,然后处理出最大值和最小值即可,由之前的贪心策略,我们可以设计一个非常有效剪枝,就是对于一种情况,若其对最大值或最小值都没有贡献,即其的值在最大值和最小值之间,则可以直接将其剪去,因为就算其后续状态可能会对最值产生贡献,但一定比当前状态下直接对最值产生贡献的情况多操作了至少一次,所以它一定不是最优的做法
其实只要这一个剪枝就足够通过本题,但我们仍更多优化方法
判断当前状态是否已经出现过,如果一个状态出现过,则其后续的所有状态都会重复,故剪枝
判断k>=len-1,对于一个长度为len的数,如果修改次数超过len-1次,则一定能修改成最小值和最大值,无需搜索
代码:
#include <iostream>
#include <queue>
#include <set>
#include <algorithm>
using namespace std;
string a;
int k;
string Min="",Max="";
struct nod
{
string s; //表示当前状态表示的数字
int cas; //表示当前状态剩余可交换次数
};
void bfs() //暴力搜索枚举所有交换情况
{
set<string>use; //判断该情况是否出现过
queue<nod>Q;
nod sta;
use.insert(a);
int len=a.size();
sta.s=a;
sta.cas=k;
Min=a;
Max=a;
Q.push(sta);
while(!Q.empty())
{
nod now=Q.front();
Q.pop();
Min=min(Min,now.s);
Max=max(Max,now.s);
if(now.cas==0) continue; //表示无法再多交换
for(int i=0;i<len;i++)
for(int j=i+1;j<len;j++)
{
if(i==0 && now.s[j]=='0') continue; //处理前导零
string tmp=now.s;
swap(tmp[i],tmp[j]);
if(tmp<=Max && tmp>=Min) //贪心策略剪枝,若一种情况对答案没有贡献,则该情况一定不是最优
continue;
if(use.count(tmp)==0) //忽略重复情况
{
nod next;
next.s=tmp;
next.cas=now.cas-1;
use.insert(tmp);
Q.push(next);
}
}
}
}
int main()
{
std::ios::sync_with_stdio(false);
int t;
cin>>t;
while(t--)
{
cin>>a>>k;
int len=a.size();
if(k>=len-1) //这种情况可直接处理出答案,虽然优化幅度很小,不加也能过...
{
Min="";
Max="";
sort(a.begin(),a.end()); //从小到大排序后,处理前导零情况,就得到最小值
int cnt=0;
for(int i=0;i<len;i++) //消除前导0
{
if(a[i]=='0') cnt++;
else
{
Min+=a[i];
while(cnt>0)
{
cnt--;
Min+='0';
}
}
}
reverse(a.begin(),a.end()); //从大到小排序,一定是最大值
Max=a;
}
else
bfs();
cout<<Min<<" "<<Max<<endl;
}
return 0;
}