相邻的数字不能相同的排列个数的求取(代码源div2每日一题:一个小整数)

做题思路记录

以下以代码源div2每日一题《一个小整数》为例

题目介绍(题目入口)

HoshiYo是一个魔术师。他擅长使用魔术,但他不擅长数学。在魔法学校的数学课上,HoshiYo学习了整数的知识。他突然发现一个有趣的事情:用他强大的魔法,他可以通过重新排列数字来改变一个整数。

从形式上看,从0∼9的每个数字,第i个数字是ai,说明有ai个这样的数。HoshiYo想得到一个符合以下规则的整数。

所有给定的数字都被使用。

第一个数不能是0,除非这个数就是0。

相邻的数字不能相同。

HoshiYo想知道他能用这些数字得到的最小整数是多少。

输入

给出10个整数a0,a1,…,a9(0≤ai≤10^5 ),表示不同数字的数量。可以保证1≤所有数总和≤10^5。

输出

输出HoshiYo能在一行中得到的最小整数。如果没有解决方案,就用一行字输出-1。

案例

样例输入
2 0 1 0 0 1 0 2 0 0
样例输出
205707

可以注意到这道题的数据比较大,直接暴力把所有情况枚举出来判断是否合法显然会超时.

这里我们分析题目要我们找出的数的特征.

首先它不能含有前导零,这个很简单,特殊处理下为0的情况即可.然后是相邻的数字不能相同,这个是问题的关键,这里也是本篇重点.最后是判断一个数的大小关系,这个很简单,对于一个位数确定的数,很明显,小的数在前面的位会拉低整个数的大小,也就是说我们要求满足情况的最小值,只需贪心处理能放在某一位的那些数中满足题目条件相邻的数字不能相同的最小值放在这个位置即可.

这里归纳一个技巧,也就是本篇重点,
该如何处理一个排列中使得相邻的数不相同的问题?

由于这种情况下一般可以知道的是这个排列中数的种类和对应在排列中出现的个数,这里我们考虑其中出现次数最多的数,仔细想想,其实一个对于一个排列,它的其中的元素个数确定了,那么如果这个出现次数最多的数比这个排列所有数/2向上取整的值还要大,那么很显然,会有多的这个数必须和自己相邻,也就不满足"排列中所有相邻的数不能相同"这个条件了,反之,则可以找到一种排列方法使之成立,用插空法思考更加清晰.这里我们假定一个有长度为5的排列,要保证相邻的数不相同,其中出现次数最多的数有3个,那么很显然,它最少有2个空要有数插里面才不会使得其相邻的数不相同,所以,此时它必须排在排列的第一个,又假设其中出现次数最多的数有2个或以下个,那么它完全可以排后面去,又假设其中出现次数最多的数有4个或更多,则很显然不可能有这样的排列存在.

所以这个题,我们可以dfs贪心处理每一位,如果能跑完所有的位,则代表肯定是存在这样的数,满足题目条件,也就是题目的答案.

Code:

void solve(){       
    vector<int>a(10);
    int tol=0;int mx=0;
    rep(i,0,9) a[i]=rd(),tol+=a[i],mx=max(mx,a[i]);
    string ans_;
    int flag=0;
    if(a[0]==1&&tol==1){
        cout<<0<<endl;
        return;
    }
    if(mx-1>tol-mx){
        puts("-1");return;
    }
    if(mx-1==tol-mx){        
        int st;
        rep(i,0,9) if(a[i]==mx) st=i;
        if(st==0) {puts("-1");return;}
        for(int i=1;i<=tol;i+=2) _[i]=st,a[st]--;
        for(int i=2;i<=tol;i+=2) rep(j,0,9) if(a[j]) {_[i]=j,a[j]--;break;}
        rep(i,1,tol) cout<<_[i];cout<<endl;
        return;
    }
    int tol_;
    function<void(int,int,string,vector<int>&)> dfs=[&](int len,int pre,string tmp,vector<int>&cnt){        
        if(len==tol){
            flag=1;
            cout<<tmp<<endl;
            return;
        }
        if(flag) return;
        mx=0;tol_=0;
        int z;        
        rep(i,0,9) {if(mx<cnt[i]){mx=cnt[i];z=i;}tol_+=cnt[i];}
        if(mx-1>tol_-mx) return;
        // if(mx==tol_-mx)         
        if(2*mx-1<tol_){
            rep(i,0,9){
                if(cnt[i]&&i!=pre) {z=i;break;}
            }
        }   
        else if(z==pre){
           return;
        }               
        // cout<<z<<endl;
        if(cnt[z]>0){
            tmp+=z+'0';
            cnt[z]--;
            dfs(len+1,z,tmp,cnt);
        }                
    };    
    int ft;
    rep(i,1,9) if(a[i]) {ft=i;break;}
    ans_+=ft+'0';a[ft]--;
    dfs(1,ft,ans_,a); 
    if(!flag) {
        puts("-1");return;
    }   
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值