二进制拆分+贪心——cf1303D

/*
先把n二进制拆分,用map保存a[i]出现次数
然后从低位到高位去凑n
对于n某位是1的位数i 
    考虑mp里是否有1<<i 
    如果没有,考虑mp里比i低位的数能否凑出i, 
    如果还是没有,就从高位去拆,只有这种情况下,才会对答案有贡献
*/
#include<bits/stdc++.h>
using namespace std;
#define ll long long 
ll n,m,a[100005],b[65];
map<ll,ll>mp;

int main(){
    int t;cin>>t;
    while(t--){
        cin>>n>>m;
        mp.clear();
        memset(b,0,sizeof b);
        
        ll sum=0;
        for(int i=1;i<=m;i++)
            cin>>a[i],mp[a[i]]++,sum+=a[i];
        for(int i=60;i>=0;i--)if((n>>i) & 1)b[i]=1;
            
        if(sum<n){puts("-1");continue;}
        
        ll ans=0;
        ll tot=0;//多余量 
        for(int i=0;i<=60;i++)
            if(b[i]){
                if(mp[(1ll<<i)]){
                    mp[(1ll<<i)]--;
                    tot+=(1ll<<i)*mp[(1ll<<i)];
                    mp[(1ll<<i)]=0;
                }
                else if(tot>=(1ll<<i)){//可以从多余量里凑 
                    tot-=(1ll<<i);
                }
                else {//必须从高阶拆 
                    ll cur=(1ll<<i);
                    ll j=cur;
                    while(1){
                        if(mp[j]!=0){
                            mp[j]--;
                            break;
                        }
                        j<<=1;
                    } 
                    while(j!=cur){
                        ans++;
                        j>>=1;
                        mp[j]++;
                    }
                    tot+=cur; 
                }
            }else {
                tot+=mp[(1ll<<i)]*(1ll<<i);
            }
        cout<<ans<<'\n'; 
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/zsben991126/p/12302048.html