CF1303D Fill The Bag

贪心,二进制

很容易想到:把 n 转化为二进制,考虑如何得到每一位。

很显然,用小的数去“凑出”大的数不花费代价,用大的数“分解”出小的数要花费代价。所以。一个简单的贪心是:设当前要得到 n 的第 i 位的数 2i,尽量用小的数凑,若小的数凑不出,再用大的数分出 2i

如何证明呢?在一些情况下,若当前要凑出的是 2i,如果按照贪心的思路,用小的来凑,可能有些小的数会被浪费,比如 32i1 只能凑出 12i,有一个 2i1 就被浪费了。所以需要证明的是:后面(从小到大遍历 i)一定不需要用这个多余的 2i1。想一想,如果后面我要用这个数,必定还需要一个 2i1 合并成 2i 后继续合并,来凑出更大的 2 的幂次(因为从小到大遍历 i,所以后面要凑出的数一定比 2i 大)。这个 2i1 又怎么来呢?又只有用一个大的数划分到 2i1但是请注意,这个大的数划分到 2i1 时,实际上会划分出两个 2i1 ,也就是现在又有 32i1,又有一个会被浪费。那我还用之前剩下的 2i1 干嘛?我不如直接用后面的两个 2i1 。所以,不管有没有浪费小的,我们都用小的去凑,如果凑不出来,就用后面存在的,最小的数去划分出 2i

Code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n;
int m;
const int MAXN=1e5+5;
ll a[MAXN];
ll read(){
    ll x=0;char c=getchar();bool f=0;
    while(c>'9'||c<'0'){f|=(c=='-');c=getchar();}
    while(c<='9'&&c>='0'){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
    return f?-x:x;
}
int cnt[105];
int main(){
    n=read(),m=(int)read();
    ll sum=0;
    for(int i=1;i<=m;i++){
        ll x=read();int t=0;
        sum+=x;
        while(x){
            if(x&1) break;
            x>>=1;t++;
        }
        cnt[t]++;
    }
    if(sum<n){
        printf("-1");
        return 0;
    }
    int ans=0;
    for(int i=0;i<=63;i++){
        ll now=1ll;now<<=i;
        if(i>0) cnt[i]+=cnt[i-1]/2;
        if(!(n&now))    continue;
        if(cnt[i])  cnt[i]--;
        else{
            for(int j=i+1;j<=63;j++){
                if(cnt[j]){
                    for(int k=i+1;k<j;k++)  cnt[k]++;
                    ans+=j-i;
                    cnt[j]--;
                    break;
                }
            }
        }
    }
    printf("%d",ans);
    return 0;
}
posted @   bwartist  阅读(5)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· Apache Tomcat RCE漏洞复现(CVE-2025-24813)
点击右上角即可分享
微信分享提示