Timus 1057. Amount of degrees 要求计算指定范围内能够由 K 个不同的 B 的幂次之和组成的整数的个数。
1057. Amount of degrees
Time Limit: 1.0 second
Memory Limit: 16 MB
Create a code to determine the amount of integers, lying in the set [
X;
Y] and being a sum of exactly
K different integer degrees of
B.
Example. Let
X=15,
Y=20,
K=2,
B=2. By this example 3 numbers are the sum of exactly two integer degrees of number 2:
17 = 2
4+2
0,
18 = 2
4+2
1,
20 = 2
4+2
2.
Input
The first line of input contains integers
X and
Y, separated with a space (1 ≤
X ≤
Y ≤ 2
31−1). The next two lines contain integers
K and
B (1 ≤
K ≤ 20; 2 ≤
B ≤ 10).
Output
Output should contain a single integer — the amount of integers, lying between
X and
Y, being a sum of exactly
K different integer degrees of
B.
Sample
Problem Source: Rybinsk State Avia Academy
解答如下:
1
using
System;
2
3
namespace
Skyiv.Ben.Timus
4
{
5
//
http://acm.timus.ru/problem.aspx?space=1
&num=1057
6
sealed
class
T1057
7
{
8
static
void
Main()
9
{
10
string
[] ss
=
Console.ReadLine().Split();
11
uint
x
=
uint
.Parse(ss[
0
]);
12
uint
y
=
uint
.Parse(ss[
1
]);
13
int
k
=
int
.Parse(Console.ReadLine());
14
int
b
=
int
.Parse(Console.ReadLine());
15
int
[] maxs
=
{
16
,
17
,
17
,
18
,
19
,
21
,
22
,
24
,
27
,
30
};
16
int
max
=
(
20
-
k
<
maxs.Length)
?
maxs[
20
-
k] :
32
;
17
uint
[,] a
=
new
uint
[k
+
1
, max];
18
for
(
int
i
=
0
; i
<=
k; i
++
) a[i,
0
]
=
1
;
19
for
(
int
j
=
0
; j
<
max; j
++
) a[
0
, j]
=
1
;
20
for
(
int
j
=
1
; j
<
max; j
++
)
21
for
(
int
i
=
1
; i
<=
k; i
++
)
22
a[i, j]
=
a[i, j
-
1
]
+
a[i
-
1
, j];
23
uint
high
=
BinarySearch(y
+
1
,
int
.MaxValue, k, b, a);
24
Console.WriteLine(high
-
BinarySearch(x, high, k, b, a));
25
}
26
27
static
uint
BinarySearch(
uint
z,
uint
high,
int
k,
int
b,
uint
[,] a)
28
{
29
uint
low
=
1
, mid
=
1
, z2
=
0
;
30
while
(low
<=
high)
31
{
32
mid
=
(low
+
high)
/
2
;
33
z2
=
GetNth(mid, k, b, a);
34
if
(z2
<
z) low
=
mid
+
1
;
35
else
if
(z2
>
z) high
=
mid
-
1
;
36
else
return
mid;
37
}
38
return
mid
+
((z2
<
z)
?
1u
:
0
);
39
}
40
41
static
uint
GetNth(
uint
n,
int
k,
int
b,
uint
[,] a)
42
{
43
bool
[] bs
=
GetNth(
new
bool
[
32
], a.GetLength(
1
)
-
1
, n, k, a);
44
if
(bs
==
null
)
return
uint
.MaxValue;
45
int
bit
=
bs.Length
-
1
;
46
while
(
!
bs[bit]) bit
--
;
47
long
v
=
0
, b2
=
1
;
48
for
(
int
i
=
0
; i
<=
bit; i
++
, b2
*=
b)
49
{
50
if
(bs[i]) v
+=
b2;
51
if
(b2
>
int
.MaxValue
||
v
>
int
.MaxValue)
return
uint
.MaxValue;
52
}
53
return
(
uint
)v;
54
}
55
56
static
bool
[] GetNth(
bool
[] bs,
int
m,
uint
n,
int
k,
uint
[,] a)
57
{
58
int
bit
=
Seek(a, k, m, n);
59
if
(bit
+
k
>=
bs.Length)
return
null
;
60
bs[bit
+
k]
=
true
;
61
if
(bit
>=
0
)
62
{
63
if
(n
>
a[k, bit]
+
a[k
-
1
, bit]) GetNth(bs, bit, n
-
a[k, bit], k
-
1
, a);
64
else
65
{
66
GetNth(bs, bit, n
-
a[k
-
1
, bit], k, a);
67
bs[bit
+
k
-
1
]
=
false
;
68
}
69
}
70
else
for
(
int
i
=
0
; i
<
k
-
1
; i
++
) bs[i]
=
true
;
//
n == 1
71
return
bs;
72
}
73
74
static
int
Seek(
uint
[,] a,
int
k,
int
m,
uint
n)
75
{
76
for
(
int
i
=
m; i
>=
0
; i
--
)
if
(a[k, i]
<
n)
return
i;
77
return
-
1
;
78
}
79
}
80
}
这道题要求计算指定范围内 (从 X 到 Y,1 ≤ X ≤ Y ≤ 231−1) 能够由 K (1 ≤ K ≤ 20) 个不同的 B (2 ≤ B ≤ 10) 的幂次之和组成的整数的个数。
这个程序的关键在于第 41 到 54 行的 GetNth 方法,该方法返回满足以下条件的第 N 个 B 进制整数:该整数中有 K 个 1,其余数字全部都是 0。该方法在第 43 行调用第 56 到 72 行的 GetNth 方法,获得表示该整数的 0 和 1 的布尔数组。然后在第 44 到 53 行根据该布尔数组计算出该 B 进制整数。
而第 56 到 72 行的 GetNth 方法是根据一定的规律计算出所需的布尔数组。该方法在第 58 行调用第 74 到 78 行的 Seek 方法获得最高位的 1 的位置,在第 60 行设置最高位的 1。然后递归调用自身以设置其余的 1,这分为两种情况。第一种情况如图中浅黄色方块所示,对应程序第 63 行,第二种情况如图中浅青色方块所示,对应程序中第 66 到 67 行。如果递归到最后达到 N = 1 的情况,则对应程序中第 70 行。
而程序中第 74 到 78 行的 Seek 方法中使用的二维数组 A 是在主程序中的第 15 到 22 行初始化的。该数组由以下递推公式决定:
A(K, 0) = A(0, N) = 1
A(K, N) = A(K, N-1) + A(K-1, N)
其中 A(K, N-1) 对应前面提到的第二种情况,A(K-1, N) 对应前面提到的第一种情况。
最后,在主程序的第 23 到 24 行调用第 27 到 39 行的 BinarySearch 方法(该方法在第 33 行调用第 41 到 54 行的 GetNth 方法),使用二分搜索法分别获得最小的 N 使得第 N 个满足条件的 B 进制整数不小于 Y+1 以及不小于 X。最终答案就是这两者之差。
这个程序使用的算法虽然比较复杂,但应该是最优的。这个程序的实际运行时间是 0.078 秒。如果使用蛮力搜索法穷举每种可能,运行时间应该会远远就超出题目限制的 1.0 秒。