类欧几里得 之 如何对具有和约束关系的两数求组合数

在写每日一题的时候遇到一个很骚的解法,而当时的每日一题抽象出来也蛮常见的 。所以去研究了一下。

原题链接:买铅笔和钢笔的方案数
题目大概是这样的:你总共只有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 penNumpenPriceTotalpencilNum×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 penPriceTotalpencilNum×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=1pencilPriceTotal(⌊penPriceTotali×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=1pencilPriceTotalpenPriceTotali×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=1nzx+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=1nzx+i×y=i=1nzzx+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=1nz(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);
  }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值