1452F Divide Powers(贪心+二进制)

这篇博客详细解析了EducationalCodeforcesRound98比赛中的F.DividePowers问题,涉及到贪心算法和二进制操作。作者首先介绍了问题背景和操作类型,接着分析了如何通过比较不同操作的投入产出比来确定最优策略。在处理大于x的数字时,如果2i-x≤k,可以完全分割;否则,需要进一步分割并递归处理。代码实现中,博主展示了如何利用O(n)的时间复杂度完成这个任务。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1452F Divide Powers(贪心+二进制)

Educational Codeforces Round 98 (Rated for Div. 2)

F. Divide Powers

题面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} 2l1

范围 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 1n30, 1q2105, 0cnti106

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} 0pos<n, 0val106, 0x<n, 1k1015

分析:通过分析可以知道,对于 1 ≤ i ≤ x 1 \le i \le x 1ix,对 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} 2ix 2 x 2^x 2x 需要执行 2 i − x − 1 2^{i-x} - 1 2ix1 次操作,投入产出比为 2 i − x 2 i − x − 1 > 1 \frac{2^{i-x}}{2^{i-x}-1} > 1 2ix12ix>1。因此在理想情况下将 i > x i>x i>x 的数字进行分割是更优的,理想情况指的是 2 i − x ≤ k 2^{i-x} \le k 2ixk

因此需要分成两种情况讨论:

2 i − x ≤ k 2^{i-x} \le k 2ixk,此时可以将该数字全部分割成 2 x 2^x 2x

2 i − x > k 2^{i-x} > k 2ix>k,此时有可能对 1 ≤ i ≤ x 1 \le i \le x 1ix 的数字 2 i 2^i 2i 进行分割会得到更优解,我们需要记录下使用这种方案的答案,然后我们再考虑对 i > x i > x i>x 的数字 2 i 2^i 2i 进行分割成两个 2 i − 1 2^{i-1} 2i1。此时又有两种情况:

  1. 2 i − 1 < = k 2^{i-1} <= k 2i1<=k,此时将 2 i − 1 2^{i-1} 2i1 完全分割成 2 x 2^x 2x 是最优的,然后再对另外一个 2 i − 1 2^{i-1} 2i1 进行处理。

  2. 2 i − 1 > k 2^{i-1} > k 2i1>k,此时不能对该 2 i − 1 2^{i-1} 2i1 进行完全分割,而是需要继续进行处理。

我们可以发现,上面的过程是递归的,每次都可以将 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】感谢观看

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值