在写每日一题的时候遇到一个很骚的解法,而当时的每日一题抽象出来也蛮常见的 。所以去研究了一下。
原题链接:买铅笔和钢笔的方案数
题目大概是这样的:你总共只有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);
}
};