Problem: M题 Water
题意
- 给两个容量分别为的杯子
- 给以下四种操作:
- 将或注满水
- 将或中水倒掉
- 喝光或杯子中的水
- 将杯子中水倒进或将杯子中水倒进
- 求每次用上述四种方法的一种,最少几次可以喝水(如果不能输出)
思路
题目就是任意的和等于,即,对于这个式子,由裴蜀定理有如果,此题无解。当此题有解时,而对于式子求解可用拓展欧几里得算法。
发现当求解出,我们操作一定是装喝,装喝,直到满足喝,这样我们的操作数就是。
如果,不妨令。则我们操作是,先装,将倒入中,如果满了,就把倒掉,直到将装满过个,同时最后一个可以不倒,这样我们的操作数就是。
解题方法
我们先用求出,因为我们算最小操作数的式子和,我们需要在接近或者接近时,可以得到最优解。(就是操作的次数,就是操作的次数,我们自己玩一玩是不是也可以感觉到越接近于只用一种杯子,就可以用更少的次数)
复杂度
- 时间复杂度:
每次得到,然后只在点附近跑很小的范围,因此每次查询复杂度是,总复杂度就是
- 空间复杂度:
没有数组,复杂度是常量级的
Code
#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
#define endl "\n"
#define ll long long
//板子
ll exgcd(ll a, ll b, ll &x, ll &y){
if(!b){
x = 1, y = 0;
return a;
}
ll d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
void solve()
{
ll a, b, x;
cin >> a >> b >> x;
ll r, s;
ll d = exgcd(a, b, r, s);
if(x % d) return cout << -1 << endl, void();
a /= d, b /= d, x /= d, r *= x, s *= x;
ll ans = 1e18;
//在附近取值
auto getans = [&](ll t){
ll n = r + t * b, m = s - t * a;
if(n >= 0 && m >= 0) ans = min(ans, 2 * (n + m));
else ans = min(ans, 2 * abs(n - m) - 1);
};
ll t0 = -r / b;
for(int i = t0 - 1; i <= t0 + 1; i++) getans(i);
t0 = s / a;
for(int i = t0 - 1; i <= t0 + 1; i++) getans(i);
cout << ans << endl;
}
int main()
{
cout.tie(nullptr);cin.tie(nullptr);ios::sync_with_stdio(false);
int _ = 1;
cin >> _;
while (_--) solve();
return 0;
}