1452F Divide Powers(贪心+二进制)
Educational Codeforces Round 98 (Rated for Div. 2)
F. Divide Powers
题意:初始给 c n t i cnt_i cnti 个 2 i 2^i 2i,现在有 q q q 个询问,有两种类型:
“ 1 p o s v a l 1~pos~val 1 pos val”,将 c n t p o s = v a l cnt_{pos} = val cntpos=val;
“ 2 x k 2~x~k 2 x k”,计算最小需要多少次操作使得至少出现 k k k 个 ≤ 2 x \le 2^x ≤2x 的数字。
每次操作可以选择一个 2 l > 1 2^l > 1 2l>1 的数进行操作,将其分割成 2 2 2 个 2 l − 1 2^{l-1} 2l−1。
范围: 1 ≤ n ≤ 30 , 1 ≤ q ≤ 2 ∗ 1 0 5 , 0 ≤ c n t i ≤ 1 0 6 1 \le n \le 30,~1\le q \le 2*10^5,~0 \le cnt_i \le 10^6 1≤n≤30, 1≤q≤2∗105, 0≤cnti≤106。
0 ≤ p o s < n , 0 ≤ v a l ≤ 1 0 6 , 0 ≤ x < n , 1 ≤ k ≤ 1 0 15 0 \le pos < n,~0 \le val \le 10^6,~0 \le x < n,~1 \le k \le 10^{15} 0≤pos<n, 0≤val≤106, 0≤x<n, 1≤k≤1015。
分析:通过分析可以知道,对于 1 ≤ i ≤ x 1 \le i \le x 1≤i≤x,对 2 i 2^i 2i 进行操作只会使满足条件的数字增加 1 1 1,投入产出比为 1 1 = 1 \frac{1}{1} = 1 11=1;而对于 i > x i > x i>x,将 2 i 2^i 2i 完全转换成 2 i − x 2^{i-x} 2i−x 个 2 x 2^x 2x 需要执行 2 i − x − 1 2^{i-x} - 1 2i−x−1 次操作,投入产出比为 2 i − x 2 i − x − 1 > 1 \frac{2^{i-x}}{2^{i-x}-1} > 1 2i−x−12i−x>1。因此在理想情况下将 i > x i>x i>x 的数字进行分割是更优的,理想情况指的是 2 i − x ≤ k 2^{i-x} \le k 2i−x≤k。
因此需要分成两种情况讨论:
① 2 i − x ≤ k 2^{i-x} \le k 2i−x≤k,此时可以将该数字全部分割成 2 x 2^x 2x。
② 2 i − x > k 2^{i-x} > k 2i−x>k,此时有可能对 1 ≤ i ≤ x 1 \le i \le x 1≤i≤x 的数字 2 i 2^i 2i 进行分割会得到更优解,我们需要记录下使用这种方案的答案,然后我们再考虑对 i > x i > x i>x 的数字 2 i 2^i 2i 进行分割成两个 2 i − 1 2^{i-1} 2i−1。此时又有两种情况:
-
2 i − 1 < = k 2^{i-1} <= k 2i−1<=k,此时将 2 i − 1 2^{i-1} 2i−1 完全分割成 2 x 2^x 2x 是最优的,然后再对另外一个 2 i − 1 2^{i-1} 2i−1 进行处理。
-
2 i − 1 > k 2^{i-1} > k 2i−1>k,此时不能对该 2 i − 1 2^{i-1} 2i−1 进行完全分割,而是需要继续进行处理。
我们可以发现,上面的过程是递归的,每次都可以将 i i i 进行下降,因此时间复杂度为 O ( n ) O(n) O(n)。
Code:
#include <bits/stdc++.h>
#define int long long
#define double long double
using namespace std;
inline int read()
{
int s = 0, w = 1;
char ch = getchar();
while (ch < '0' || ch > '9')
{
if (ch == '-')
w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
s = s * 10 + ch - '0', ch = getchar();
return s * w;
}
const int MAXN = 50 + 10;
const int INF = 0x7f7f7f7f;
const int MOD = 1e9 + 7;
const double eps = 1e-9;
const double PI = acos(-1.0);
int n, q;
int cnt[MAXN];
signed main()
{
n = read(), q = read();
for (int i = 0; i < n; i++)
{
cnt[i] = read();
}
for (int t = 0; t < q; t++)
{
int op = read();
if (op == 1)
{
int pos = read(), val = read();
cnt[pos] = val;
}
else
{
int x = read(), k = read();
int num = 0; // 初始满足条件的数量
int small = 0; // 1~x中最多可以进行操作的次数
int res = 0;
for (int i = 0; i <= x; i++)
{
small += cnt[i] * ((1ll << i) - 1);
num += cnt[i];
}
if (num >= k)
{
cout << 0 << endl;
continue;
}
k -= num;
// 先贪心地将 i>x 的数字 2^i 分割来减少 k
int idx;
for (idx = x + 1; idx < n; idx++)
{
if (cnt[idx])
{
int y = (1ll << (idx - x));
int need = min(cnt[idx], k / y);
res += need * (y - 1);
k -= need * y;
small += need * y * ((1ll << x) - 1);
if (need < cnt[idx])
{
break;
}
}
}
if (k <= 0)
{
cout << res << endl;
continue;
}
// 如果没有 i>x 的数字 2^i,则只能使用 small
if (idx >= n)
{
if (small >= k)
{
cout << res + k << endl;
}
else
{
cout << -1 << endl;
}
continue;
}
// 递归处理
int ans = 1e18;
while (idx > x)
{
if (small >= k)
{
ans = min(ans, res + k);
}
res++;
idx--;
int y = (1ll << (idx - x));
if (y <= k)
{
res += y - 1;
k -= y;
small += y * ((1ll << x) - 1);
}
}
cout << min(ans, res) << endl;
}
}
return 0;
}
【END】感谢观看