在写每日一题的时候遇到一个很骚的解法,而当时的每日一题抽象出来也蛮常见的 。所以去研究了一下。
原题链接:买铅笔和钢笔的方案数
题目大概是这样的:你总共只有Total
块钱,要用这钱去买笔。铅笔一支pencilPrice
元,钢笔一支penPrice
元。你可以买任意数量的铅笔和钢笔,只要钱够;不全部用完也行。
求你可能有几种买法?
这里约定铅笔的数量记为pencilNum
,钢笔的数量记为penNum
。所以如果你先买了铅笔,那么剩下能买的钢笔的数量就不会超过一个确定的数。所以这里,能购买的铅笔数量和钢笔的数量形成和约束关系:
p
e
n
N
u
m
⩽
⌊
T
o
t
a
l
−
p
e
n
c
i
l
N
u
m
×
p
e
n
c
i
l
P
r
i
c
e
p
e
n
P
r
i
c
e
⌋
penNum \leqslant \lfloor \frac{Total - pencilNum \times pencilPrice}{penPrice} \rfloor
penNum⩽⌊penPriceTotal−pencilNum×pencilPrice⌋
只要钢笔总数不超过上面这数字,就可以随便买,所以当知道了买的铅笔的数量时,对应的方案数就可以很轻易求出: ⌊ T o t a l − p e n c i l N u m × p e n c i l P r i c e p e n P r i c e ⌋ + 1 \lfloor \frac{Total - pencilNum \times pencilPrice}{penPrice} \rfloor + 1 ⌊penPriceTotal−pencilNum×pencilPrice⌋+1
那么,只要枚举可以购买的铅笔的数量,就能计算出所有方案数了。
可以表示为:
∑
i
=
1
⌊
T
o
t
a
l
p
e
n
c
i
l
P
r
i
c
e
⌋
(
⌊
T
o
t
a
l
−
i
×
p
e
n
c
i
l
P
r
i
c
e
p
e
n
P
r
i
c
e
⌋
+
1
)
\sum_{i=1}^{\lfloor \frac{Total}{pencilPrice}\rfloor} (\lfloor \frac{Total - i \times pencilPrice}{penPrice} \rfloor + 1)
i=1∑⌊pencilPriceTotal⌋(⌊penPriceTotal−i×pencilPrice⌋+1)
好耶,得到了这个公式,其实题目就已经可以爆破了。但是本篇内容介绍类欧几里得算法,所以还要进行一些额外工作。
我们知道上面的式子,关键在于+1
前面的部分。
∑
i
=
1
⌊
T
o
t
a
l
p
e
n
c
i
l
P
r
i
c
e
⌋
⌊
T
o
t
a
l
−
i
×
p
e
n
c
i
l
P
r
i
c
e
p
e
n
P
r
i
c
e
⌋
\sum_{i=1}^{\lfloor \frac{Total}{pencilPrice}\rfloor} \lfloor \frac{Total - i \times pencilPrice}{penPrice} \rfloor
i=1∑⌊pencilPriceTotal⌋⌊penPriceTotal−i×pencilPrice⌋
使用更简单一些的形式表示就是:
∑
i
=
1
n
⌊
x
+
i
×
y
z
⌋
=
f
(
x
,
y
,
z
,
n
)
\sum_{i=1}^{n} \lfloor \frac{x + i \times y}{z} \rfloor = f(x, y, z, n)
i=1∑n⌊zx+i×y⌋=f(x,y,z,n)
我们用小学二年级学的高数对上面求和的式子进行一些展开和变换:
原式
=
∑
i
=
1
n
⌊
x
+
i
×
y
z
⌋
=
∑
i
=
1
n
⌊
x
z
+
x
m
o
d
z
+
y
z
×
i
+
y
m
o
d
z
×
i
z
⌋
原式=\sum_{i=1}^{n} \lfloor \frac{x + i \times y}{z} \rfloor \\ = \sum_{i=1}^{n} \lfloor \frac{\frac{x}{z} + x \mod z + \frac{y}{z} \times i + y \mod z \times i}{z} \rfloor
原式=i=1∑n⌊zx+i×y⌋=i=1∑n⌊zzx+xmodz+zy×i+ymodz×i⌋
把与i无关的部分直接提出来:
原式
=
(
n
+
1
)
⌊
x
z
⌋
+
n
×
(
n
+
1
)
2
⌊
y
z
⌋
+
∑
i
=
1
n
⌊
(
x
m
o
d
z
)
+
(
y
m
o
d
z
)
i
z
⌋
=
(
n
+
1
)
⌊
x
z
⌋
+
n
×
(
n
+
1
)
2
⌊
y
z
⌋
+
f
(
y
m
o
d
z
,
x
m
o
d
z
,
z
,
n
)
原式=(n+1)\lfloor \frac{x}{z}\rfloor +\frac{n \times (n + 1)}{2} \lfloor \frac{y}{z}\rfloor + \sum_{i=1}^{n} \lfloor \frac{(x \mod z) + (y \mod z)i}{z} \rfloor \\ = (n+1)\lfloor \frac{x}{z}\rfloor +\frac{n \times (n + 1)}{2} \lfloor \frac{y}{z}\rfloor + f(y\mod z, x \mod z, z, n)
原式=(n+1)⌊zx⌋+2n×(n+1)⌊zy⌋+i=1∑n⌊z(xmodz)+(ymodz)i⌋=(n+1)⌊zx⌋+2n×(n+1)⌊zy⌋+f(ymodz,xmodz,z,n)
这样一来,就能非常快速地求出总和。
对应到c++也很好实现:
class Solution
{
public:
long waysToBuyPensPencils(int total, int cost1, int cost2)
{
function<long(long, long, long, long)> f = [&f](long a, long b, long c, long n)
{
if (!a) return (n + 1) * (b / c);
if (a >= c || b >= c) return n * (n + 1) / 2 * (a / c) + (n + 1) * (b / c) + f(a % c, b % c, c, n);
long m = (a * n + b) / c;
return n * m - f(c, c - b - 1, a, m - 1);
};
if (cost1 > cost2)
swap(cost1, cost2);
long n = total / cost1;
return 1 + n - n * (n + 1) / 2 + f(cost2 - cost1, total, cost2, n);
}
};