题目描述
有一台用电容组成的计算器,其中每个电容组件都有一个最大容量值(正整数)。
对于单个电容,有如下操作指令:
- 指令1:放电操作 - 把该电容当前电量值清零;
- 指令2:充电操作 - 把该电容当前电量补充到最大容量值;
- 指令3:转移操作 - 从电容A中尽可能多的将电量转移到电容B,转移不会有电量损失,
如果能够充满B的最大容量,那剩余的电量仍然会留在A中。
现在已知有两个电容,其最大容量分别为a和b,其初始状态都是电量值为0,希望通过一系列
的操作可以使其中某个电容(无所谓哪一个)中的电量值等于c(c也是正整数),这一系列操作
所用的最少指令条数记为M,如果无论如何操作,都不可能完成,则定义此时M=0。显然对于每一
组确定的a,b,c,一定会有一个M与之对应。
输入描述:
每组测试样本的输入格式为:
第一行是一个正整数N;
从第二行开始,每行都是3个正整数依次组成一组a,b,c,一共有N组;
输出描述:
输出为N行,每行打印每一组对应的M
样例
输入:
2
3 4 2
2 3 4
输出:
4
0
说明
对于(3,4,2),最少只需要4个指令即可完成:
(设最大容量为3的是A号电容,另一个是B号电容)
操作:(充电A)=> (转移A -> B) => (充电A)=> (转移A -> B)。
对于(2,3,4),显然不可能完成,输出0。
备注:
数据范围:
N:0<N<100;
a,b,c
0<a,b,c<105(50%)
0<a,b,c<107(30%)
0<a,b,c<109(20%)
算法设计与分析
(扩展欧几里得算法,裴蜀定理)
时间复杂度
: O(NlogV)
此题为LeetCode 365. Water and Jug Problem的扩展版。
首先,根据Y总的LeetCode 365. 水壶问题(LeetCode究极班)视频讲解,我们可以将两个电容的状态视为一个整体
进行考虑。那么可以证明:
引论1:这个整体
与外部之间的交互只需要包含+a、−a、+b、−b四种操作。
简略证明:假设这个整体
还需要中间数值的电容量q(即0<q<a或0<q<b)与外界交互。
那么在充入(或者释放)q电量之前,整体
的状态只可能是以下两种情况之一:
1. 一个电容为空,另一个电容半满不满
;
2. 一个电容为满,另一个电容半满不满
我们简单分析一下情况1(情况2的分析类似)。在情况1的状态下,如果是充入电量q将整体
状态变为一空一满
,那么我们可以从初始状态
通过前面讲的四种操作
达到同样的状态,
并且操作次数更少。也就是说为了到达目标状态
(一空一c
或者一满一c
),从初始状态
开始
变换,整体
与外界的交互只需要+a、−a、+b、−b四种操作就足够了。
在证明完引论1之后,那么问题就转换成了:等式ax+by=c是否存在整数解。x,y的符号
为正代表充电,为负代表放电。根据裴蜀定理
,等式成立当且仅当gcd(a,b)|c。
我们现在来考虑当gcd(a,b)|c时,如何计算最少操作次数:
根据裴蜀定理,存在整数x,y,使得ax+by=c,且由于c≤Max(a,b),所以x,y一定一正一负
,
或者一正一零
。
对于一正一零
的情况,
1. 如果是x>0,y=0(即a|c),那么a≤c≤b,若此时a=c,那么只需充电1次;若此时a<c,
那么需要的充电次数为x次,放电次数为0次,转移次数为x次,总共操作次数为2x次。
2. 对于x=0,y>0的情况类似,若此时b=c,总共操作次数只需1次;否则,总共操作次数为2y次。
接着,我们继续分析一正一负
的情况:
此时a,b,c三者之间的关系可能如下(由于前面已经分析过一正一零
的情况,这里不
需要再考虑c=b或者c=a的情况):
- a<c,那么此时应该有c<b;
- c<a,
2.1. b<c;
2.2. b>c,此时
2.2.1. c<a<b
2.2.2. c<b<a
不妨先假设x>0,然后根据上述a,b,c的大小关系逐一分析。
1. 先分析a<c<b时。此时,由于x>0并且a<b,那么操作序列就是:
+a,转移,+a,转移,…,+a,转移,−b,转移,+a,转移,…
由于a<c,那么最终是B里存储着目标电量c。根据上述操作序列,一共有x次充电操作,−y次放电操作,x−y次转移操作,总操作次数为2x−2y。注意,上述操作序列里−b之后的转移
操作,此次的转移
操作所转移的电量是小于
b的;在这之前的转移
操作都是从A转
移电量到B,并且将B加满
,这是为了保证整体
与外界之间的操作只包含+a、−a、+b、−b这四种操作。注意,由于我们前面已经分析完了一正一零
的情况,所以−b放电后的转移
操作必不可少。
2. 然后,我们分析b<c<a(即2.1.)的情况。其操作序列大概如下:
+a,转移,−b,转移,−b,转移,…,转移,−b,转移,+a,转移,−b,转移,…
此时,电容A里首先出现c电量(这是由我们前面的假设x>0所决定的)。上述序列里,
每个+a操作后都跟着一个转移
操作,每个−b操作后跟着一个转移
操作。但请注意,根据题意,
当电量c出现在电容A里时,电容B里无需再进行本次的−b操作,从而操作序列里跟随在本次−b操作
后的转移
操作也无从谈起。这样,充电操作进行了x次,放电操作进行了−y−1次,转移操作进行
了x−y−1次,最终整体
里的电量状态为电容A里存储着c电量,电容B里存储着b电量
,总共的操作次数为2x−2y−2次。
3. 对于c<a且c<b的情况分析逻辑是类似的,在此就不再赘述了,直接给出结论:
总共的操作次数为2x−2y−2次。
在分析完上述一正一零
和一正一负
的情况后,我们发现,只需要最小化|x|+|y|的值。可以先用
扩展欧几里得算法求出任意一组(x,y),则
{x+kb/gcd(a,b)y−ka/gcd(a,b),k=…,−2,−1,0,1,2,…
就是该等式的全部整数解,我们找出和0最接近的两组(x,y)(一组x>0, 另一组y>0,然后取操作次数
较小的一组即可)
时间复杂度分析
:扩展欧几里得算法的复杂度是O(logV),一共 N组测试数据,所以总时间复杂度是O(NlogV)。
C++ 代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
LL gcd(LL a, LL b, LL &x, LL &y)
{
if (b == 0)
{
x = 1, y = 0;
return a;
}
LL q = gcd(b, a % b, y, x);
y -= a / b * x;
return q;
}
int main()
{
int T;
cin >> T;
while (T--)
{
LL a, b, c, x, y;
cin >> a >> b >> c;
int d = gcd(a, b, x, y);
if (c > a && c > b || c % d)
{
cout << 0 << endl;
continue;
}
if (c == a || c == b)
{
cout << 1 << endl;
continue;
}
if (y > 0) swap(x, y), swap(a, b);
LL a2 = a / d, b2 = b / d;
x *= c / d, y *= c / d;
LL k = x / b2;
x -= k * b2, y += k * a2;
LL res;
if (c > a) res = 2 * (x - y);
else res = 2 * (x - y - 1);
x -= b2, y += a2;
if (c > b) res = min(res, 2 * (y - x));
else res = min(res, 2 * (y - x - 1));
cout << res << endl;
}
return 0;
}