# C

# 题目大意

初始时,有 nn 个苹果,我要吃 kk 个苹果
每次我可以花费 11 把一堆苹果分成 x2\lfloor{\frac{x}{2}}\rfloorx2\lceil{\frac{x}{2}}\rceil,如果恰好出现了 kk,则我就会吃,问我的最小花费

# 数据范围

  • 1n,k1091 \le n, k \le 10^9

# 题解

可以发现,每一层都是递减的,也就是说,ii 层的最小值不会小于 i1i-1 层的最大值,所以我们可以从顶层往下遍历,维护每层最小值 ll 和最大值 rr,直到搜到 kkl,rl, r 搜完了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void solve() {
cin >> n >> k;

if (n == k) {
cout << 0 << '\n';
return;
}

int l = n, r = n;

for (int i = 1; i <= 30; i++) {
l = l / 2, r = (r + 1) / 2;

if (l <= k && k <= r) {
cout << i << '\n';
return;
}
}

cout << -1 << '\n';
}

# D

# 题目大意

给定整数 n,n=2dn,~n = 2^d,其中 dd 是非负整数,可以任意从中选取一个整数 xx
现在有两种操作

  1. x=x/2x = x / 2,仅当 xx 是偶数时候可以进行此操作
  2. x=x1x = x - 1,随意操作
    请问 1>n1->n 中,有多少个 xx,在经过 kk 次操作时候,仍不未 00(操作人绝顶聪明)

# 数据范围

  • 1n,k1091 \le n,k \le 10^9

# 题解

对于绝顶聪明的操作者来说,xx 想要变成 00,最优的操作肯定是
log2x+popcount(x)\lfloor{\log_2 x}\rfloor + popcount(x)
也就是能减半就减半,不能减半的就是 xx 中的二进制表达形式中 11 的个数(也就是说,一个数的操作次数是这个数在二进制下的位数加二进制下 11 的数量再 1-1

那么我们可以去枚举 len2(x)len_2(x)cnt1cnt_1
nn22dd 次方,二进制是 10..010..0,其中 00dd
那我们不妨先对 d1d - 1 去做,然后再特判下 nn

对于 1(n1)2(1..1)1 \to (n - 1)_2(1..1),长度是 dd
我们可以枚举长度 lenlen,和 11 的个数 1len1 \to len,然后要让第一个是 11,所以如果 len+cnt11>klen + cnt_1 - 1 > k,那么答案就加上 Clen1cnt11C_{len - 1}^{cnt_1 - 1}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void solve() {
cin >> n >> k;

int len = 0, nn = n, res = 0;
while (nn)
len++, nn >>= 1;

for (int l = 1; l < len; l++) {
for (int cnt1 = 1; cnt1 <= l; cnt1++)
if (l + cnt1 - 1 > k)
res += C[l - 1][cnt1 - 1];
}

cout << res + (len > k) << '\n';
}

这里学到了个初始化组合数(小范围)的新方法

C++
1
2
3
4
5
for (int i = 0; i < 30; i++) {
C[i][0] = 1;
for (int j = 1; j <= i; j++)
C[i][j] = C[i - 1][j] + C[i - 1][j - 1];
}

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

duoxichangan 微信支付

微信支付

duoxichangan 支付宝

支付宝