CF-Round 80-div2-D题

CF-Round 80-div2-D题

D. Fill The Bag

传送门

这道题是二进制+贪心。
是我数次tle和wa之后。。。害。别提绿的时候多开心了。癫狂: )

题目大意:给你一个序列代表一些物品。都是2的次幂。然后给你一个容量为n的包。要求你要把包全都填充满。可以分割这些物品(每次是/2分割)
问最小需要分割多少次才可以把包填满。

贪心策略:我们把这些物品的二进制的最高位存起来。
我们把所有的物品加起来特判一下:
如果背包容量大于这个和。那么肯定不行。输出-1即可。

对应的数最高的二进制位作为下标。计数。cnt[]
然后进行对n的二进制操作。从低位往高位走。
如果碰到当前不为0,代表填充背包我们需要这个数的物品。
看看cnt[]对应的位置是否为0
如果不为0.直接–
如果为0.那么我们需要找到最近的高位将那个数字分解。
并且维护更新cnt[](将数字分解之后对应的cnt[]肯定要更新嘛)
比如:我们需要4但是cnt[2] = 0。当cnt[3] != 0的时候。我们要把8拆掉。如果拆成了两个4。4 = 2^2.那么我们的cnt[2]肯定要++嘛。
这里解释一下为什么只++一次:因为n的二进制表示只有1或者0.同一位的地方只需要一次或者不需要。所以只需要更新一次即可。

最后我们贪心的把当前位的数字贡献给下一位:
cnt[i + 1] += cnt[i] / 2;
代表2个2^i次方可以组成2 ^ (i + 1)次方。

if ((1ll << i) & n)这式子是代表看看n的二进制位哪些为1的。

注意:循环条件到60就足够了。。我wa的时候循环条件搞大了。然后他说我移位太大了emmm.
tle的时候我就很迷=-=emmm。不知道咋了。
然后特别注意ll。
心酸qwq

代码部分:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;

ll t;
ll cnt[70];
ll n;
ll m;

int main()
{
	scanf ("%I64d", &t);
	while (t--)
	{
		ll s = 0, ans = 0;
		scanf ("%I64d%I64d", &n, &m);
		for (int i = 0; i <= 30; i++)
		{
			cnt[i] = 0;
		}
		for (ll i = 1; i <= m; i++)
		{
			ll x, t = 0;
			scanf ("%I64d", &x);
			s += x;
			cnt[(int)log2(x)]++;
		}
		if (n > s)
		{
			cout << "-1\n";
			continue;
		}
		for (int i = 0; i <= 60; i++)
		{
			if ((1ll << i) & n)
			{
				if (cnt[i])
				{
					cnt[i]--;
				}
				else
				{
					int j;
					for (j = i + 1; j < 64; j++)
					{
						if (cnt[j])
						{
							break;
						}
					}
					cnt[j--]--;
					for (; j >= i; j--)
					{
						ans++;
						cnt[j]++;
					}
				}
			}
			if (i < 60)
			{
				cnt[i + 1] += cnt[i] / 2;
			}
		}
		cout << ans << endl;
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

娃娃酱斯密酱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值