冒险者分队是人气 MMORPG《最终幻想 14》里的一个游戏系统。玩家通过招募 NPC (非玩家角色)组成小队完成特定任务后可以获取丰厚的奖励。
由于完成任务有能力的要求,因此我们需要对 NPC 进行一定的训练。NPC 组成的小队会有三个属性:体能、心智,以及战术,玩家可以选择以下的两种训练课程之一对小队进行训练:
- 提升其中一个属性 40,降低其他两个属性各 20;
- 提升其中两个属性 20,降低剩下一个属性 40。
如果在选择的训练课程后有任意一个属性小于 0,那么训练会失败,属性不会发生变化。
为了完成特定任务,现在给定小队的初始属性和目标属性,请回答是否有可能通过一定的训练,使得小队的属性正好达到目标属性的值,如果可以的话,最少的次数是多少?
输入格式:
输入第一行是一个正整数 T (≤105),表示有多少组询问。
接下来的 T 组询问,每组询问有两行,每行三个非负整数,第一行为小队初始的属性,第二行为需要达成的目标属性。
所有属性值均大于等于 0,小于等于 2×109。
输出格式:
如果目标属性无法通过训练达到,输出一行 −1,否则输出一个整数,表示达到目标属性的最少训练次数。
输入样例:
4
25 30 35
65 10 15
100 200 300
200 180 220
100 100 100
0 0 0
777 888 999
777 888 999
输出样例:
1
3
-1
0
一、题意理解
归结为数学问题,用目标值减去初始值,得到差值,考虑如何用给出的变化方式构造差值。同时考虑非法情况。
二、官方解题思路&&批注
1.不妨考虑两种训练的属性值的变化特征,不难发现都是 20 的倍数,所以可以将操作简化为:
* 一个属性 +2,两个属性 -1;
* 一个属性 -2,两个属性 +1。
恰好,无论是 +2、-1,还是 -2、+1,对 3 取模时结果都一致,因此操作无法改变属性值差的取模结果,这是判定可行性的重要标准之一。
另外,由于训练的属性变化总和均为 0,因此属性值的和也不应该发生变化。也就是说,差值的和应该为 0。(注意-2%3=-2)
综上,我们先确定什么情况有可行解:
1. 显然差值必须是 20 的倍数;
2. 第一条满足的情况下,除以 20,对 3 取模答案相同;
3. 第二条满足的情况下,差值的和为 0。
2、在解决可行解的问题后,我们需要解决最小训练次数的问题。
假设三个属性的差值分别为 a1, a2, a3,除去直接得到答案的 a1=a2=a3=0,因为差值和为 0,所以一定至少有一个正数以及一个负数,并且可以通过全部取负值的方式得到两个负数以及一个正数。不妨假设有 a1 < a2 < 0 < a3。(因为操作取负对称,所以不改变结果)
我们的目标显然是让 a1、a2以及a3等于0。我们首先考虑让a3尽快变成0,这样的话可以利用 (+1, +1, -2) 的操作先进行操作,但由于 a3 = -(a1 + a2),因此在 a3 变成 0 的过程中,a2 会先变成 0,这时有 a3 = -a1。我们此时再使用 (+2, -1, -1) 以及 (+1, +1, -2) 的两步操作,组合出 (+3, 0, -3) 的操作,完成剩下的调整。
不难看到如果只使用 (+1, +1, -2) 以及 (+3, 0, -3) 两种操作的话,这样做的答案是最小的。如何证明这两个操作能构造出最优解,交给有兴趣的同学证明。(贪心)
三、代码&&思路
1.按照3条判断非法状态
2.处理成x <y < 0 <z
3.y先会变成0——res+=-y,更新x z;
4.res += z / 3 * 2;——通过(3,-3) 让x z变成0
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <math.h>
#include <map>
#include <set>
#include <queue>
using namespace std;
#define endl '\n'
void solve(){
int a,b,c,x,y,z;
cin >> a >> b >> c >> x >> y >> z;
int dx = a - x,dy = b - y,dz = c - z;//处理差值
if (dx % 20 || dy % 20 || dz % 20 || a + b + c != x + y + z)//1.1 不是20的倍数return
{
cout << -1 << endl;
return;
}
dx /= 20,dy /= 20,dz /= 20;
int mod = (dx % 3 + 3) % 3;
if ((dy % 3 + 3) % 3 != mod || (dz % 3 + 3) % 3 % 3 != mod)//1.2 mod3不为1 return
{
cout << -1 << endl;
return;
}
int num = 0;
num += (dx > 0) + (dy > 0) + (dz > 0);
if (num == 2) dx = -dx,dy = -dy,dz = -dz;// 转化为标准形式
x = min({dx,dy,dz}),z = max({dx,dy,dz}),y = dx + dy + dz - x - z;//排序
int res = 0;
res += -y,x -= y,z += y * 2,y = 0;//2.1 通过+1让y变成0,更新x z
res += z / 3 * 2;//2.2 通过(3,-3) 让x z变成0
cout << res << endl;
}
signed main(){
std::ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int t;
cin >> t;
while (t--) solve();
}
四、总结
1.在非法判断条件中:“除以 20,对 3 取模答案相同”,这一条比较难想,可以积累
2.本质上是数学问题,不要畏惧