题目
给你一个十进制数n(n<=1e9),一个数k
自己可以和自己交换,
也可以理解成可以不用次数
问最多k次交换后,
这个数n最小最大各是多少
思路来源
https://www.cnblogs.com/xiuwenli/p/9432817.html
题解
思路来源的题主代码写的好评,赞一个
时间卡的非常紧 2500ms的题2300ms过了
毕竟全排列10!*100跑满的话要3e8多
考虑暴力枚举每个数,
如果没有前导0且符合最小交换小于等于k次
就更新一下最大最小值
怎么统计最小交换次数呢,
如果单纯把每一位拆开跑全排列,
可能会有重复的数,这样统计的时候会有麻烦
不如我们跑第i位的全排列,即下标的全排列,就不会重复
然后找到每个下标的环
比如说原来是01234(pos数组)
某个排列中是10342,
这样10构成了一个环,342构成了一个环
不难发现,每个环两两交换使之还原成原来的样子,
所需的次数是环里的数个数-1,
因为除了最后一次,
之前的每一次只能使一个数复位
心得
以后题死抠抠不对的时候,及时看题解,或许真的是自己想不到的解法
好像暑假的时候做过这个题,当时没补,在6个月后又惨遭爆锤
之前想过一个把右起最大和左起最小换,然后向低位扫搞最大值
第一次特判前缀0把右起最小和左起最大换,再向后类似的不特判0搞最小值的做法
现在发现不行,毕竟右起和左起换,是可能会破坏原本的最小环结构的
这样操作可能使得把两个小环合成一个大环,从而交换更多次
具体反例也举不出来,就这样叭
另外那个i=pos[i]的找环操作真是惊艳吖
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=11;
int t,k;
char s[maxn];
int num[maxn];
int pos[maxn];
bool vis[maxn];
ll ans1,ans2,n;
//01234 原pos数组
//10342 某个全排列
//则i=pos[i]会找到环
//每个cnt的节点的环需要cnt-1次交换才能归位
bool check(int len)
{
int res=0,cnt=0;;
memset(vis,0,sizeof(vis));
for(int i=0;i<len;++i)
{
if(vis[i])continue;
while(!vis[i])
{
cnt++;
vis[i]=1;
i=pos[i];
}
res+=cnt-1;
if(res>k)return 0;
cnt=0;
}
return 1;
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%s%d",s,&k);
int len=strlen(s);
n=0;
for(int i=0;i<len;++i)
{
num[i]=s[i]-'0';
n=n*10+num[i];
pos[i]=i;
}
ans1=ans2=n;
//注意全排列对下标全排列,不要对num[]全排列
//num中有相同的元素不好处理 下标一定不会重复
do
{
ll tmp=0;
for(int i=0;i<len;++i)
tmp=tmp*10+num[pos[i]];
if(num[pos[0]]&&check(len))//无前导零且可行
{
if(tmp<ans1)ans1=tmp;
if(tmp>ans2)ans2=tmp;
}
}while(next_permutation(pos,pos+len));
printf("%lld %lld\n",ans1,ans2);
}
return 0;
}