codeforces D. Fill The Bag(二进制+贪心)

传送门

题意:

给出一个数 n n n ,和 m m m个2的幂次方的数。每次操作可以将其中一个数拆成相等的两部分,即除以2,求最少的操作次数,使得最后可以选一些值使其总和为 n n n

题解:

如果所有数总和不够 n n n ,那么就一定不能构成。

先统计这 m m m个数每个二进制位上有几个 1 1 1 。从低位到高位枚举 n n n的每一位,若当前位为 1 1 1

1. 1. 1. 若这一位 1 1 1的数量足够,那么消耗 1 1 1 ,剩下的合并成高位

2. 2. 2. 若这一位1的数量为0,那么在最近的高位中找的一个 1 1 1 ,然后拆分。

若若当前位为 0 0 0 ,则直接向高位合并即可。

**代码 : **

#pragma GCC diagnostic error "-std=c++11"
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<stack>
#include<set>
#include<ctime>
#define iss ios::sync_with_stdio(false)
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
typedef pair<ll,int> pii;
const int mod=1e9+7;
const int MAXN=2e5+5;
const int inf=0x3f3f3f3f;
ll a[MAXN];
int vis[70];
int num[70];
void work()
{
    memset(vis, 0, sizeof vis);
    memset(num, 0, sizeof num);
    ll n;int m;
    cin >> n>>m;
    ll sum = 0;
    for (int i = 1; i <= m;i++){
        cin >> a[i];
        sum += a[i];
        num[(int)log2(a[i])]++;
    }
    if(sum<n)
    {
        printf("-1\n");
        return;
    }
    int cnt = 0;
    while(n)
    {
        if(n&1)
            vis[cnt] = 1;
        cnt++;
        n /= 2;
    }
    int ans = 0;
    for (int i = 0; i <= 64;i++)
    {
        if(!vis[i])
            num[i + 1] += num[i] / 2;
        else
        {
            if(num[i])
                num[i + 1] += (num[i]-1) / 2;
            else
            {
                for (int j = i + 1; j <= 64;j++)
                {
                    if(num[j])
                    {
                        ans += j - i;
                        for (int k = j - 1; k > i;k--)
                            num[k]++;
                        num[j]--;
                        break;
                    }
                }
            }
        }
    }
    printf("%d\n", ans);
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        work();
    }
}
posted @ 2021-08-15 11:21  TheBestQAQ  阅读(38)  评论(0)    收藏  举报