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;
}