Luggage Lock( The 2021 ICPC Asia Shenyang Regional Contest )
题面描述:
Eileen has a big luggage and she would pick a lot of things in the luggage every time when A-SOUL goes out for a show. However, if there are too many things in the luggage, the 4-digit password lock on the luggage will be hard to rotate.
The state of lock is the four digits on the lock. In one step, she can choose consecutive digits to rotate up by one simultaneously or down by one simultaneously. For example, she can rotate 0000 \texttt{0000} 0000 to 0111 \texttt{0111} 0111 or 0900 \texttt{0900} 0900 in one step because the rotated digits are consecutive, but she can’t rotate 0000 \texttt{0000} 0000 to 0101 \texttt{0101} 0101 in one step. Since she has little strength, she wants to rotate the lock as few times as possible.
Now the lock is at state a 0 a 1 a 2 a 3 a_0a_1a_2a_3 a0a1a2a3 and the password is b 0 b 1 b 2 b 3 b_0b_1b_2b_3 b0b1b2b3. As a fan of A-SOUL, you are asked to help Eileen find out the optimal plan to unlock but you only need to tell Eileen how many times she has to rotate.
input
The first line contains one integer T T T ( 1 ≤ T ≤ 1 0 5 ) (1 \leq T \leq 10^5) (1≤T≤105), denoting the numer of test cases.
Each of the test cases contains a line containing the initial state a 0 a 1 a 2 a 3 a_0a_1a_2a_3 a0a1a2a3 and the target state b 0 b 1 b 2 b 3 b_0b_1b_2b_3 b0b1b2b3.
output
For each test case, output a line containing a single integer, denoting the minimum steps needed to unlock.
Exemple
6
1234 2345
1234 0123
1234 2267
1234 3401
1234 1344
1234 2468
1
1
4
5
1
4
题目大意
Eileen有一个大行李箱,每次出门时她需要从行李箱中拿很多东西。然而,如果行李箱内放置的物品太多,四位数密码锁的旋转就会变得困难。
密码锁的状态是四个数字。在每一步中,Eileen可以选择连续的数字,将它们同时向上或向下旋转1。比如,她可以将0000
旋转到0111
或0900
,但不能将0000
旋转到0101
,因为它们不是连续的数字。由于她力量有限,她希望以最少的步骤旋转密码锁。
现在,密码锁的状态是a0a1a2a3
,目标密码是b0b1b2b3
。作为A-SOUL的粉丝,你被要求帮助Eileen找出解锁的最优方案,但你只需要告诉Eileen需要多少步旋转才能解锁。
思路
这是一道很明显的bfs+模拟的题(和八数码是一类题)
其实可以采用离线,并且理解题目为从“0000”到目标状态的旋转次数!!!
比如说:“1234”到“2345”就相当于从“0000”到“1111”或者说“9999”
因为密码旋转本身就是环形结构,所以说可以一样操作。
因 为 起 点 和 终 点 都 不 一 样 , 还 有 1 e 5 个 测 试 样 例 。 因 此 不 能 直 接 对 每 一 组 样 例 都 做 一 遍 b f s , 肯 定 会 超 时 。 因为起点和终点都不一样,还有1e5个测试样例。因此不能直接对每一组样例都做一遍bfs,肯定会超时。因为起点和终点都不一样,还有1e5个测试样例。因此不能直接对每一组样例都做一遍bfs,肯定会超时。
因 此 我 们 可 以 将 所 有 测 试 样 例 的 起 点 都 转 化 为 0000 , 终 点 对 应 变 为 t [ i ] − s [ i ] ( s 为 起 点 , t 为 终 点 ) 因此我们可以将所有测试样例的起点都转化为0000,终点对应变为t[i]-s[i](s为起点,t为终点)因此我们可以将所有测试样例的起点都转化为0000,终点对应变为t[i]−s[i](s为起点,t为终点)
这 样 转 换 之 后 , 我 们 可 以 以 0000 作 为 起 点 进 行 一 遍 b f s , 同 时 记 录 下 到 达 每 个 状 态 所 需 的 步 数 。 这 样 对 于 这样转换之后,我们可以以0000作为起点进行一遍bfs,同时记录下到达每个状态所需的步数。这样对于这样转换之后,我们可以以0000作为起点进行一遍bfs,同时记录下到达每个状态所需的步数。这样对于每 个 测 试 样 例 即 可 O ( 1 ) 输 出 答 案 。 每个测试样例即可O(1)输出答案。每个测试样例即可O(1)输出答案。
代码实现
#include <map>
#include <set>
#include <fstream>
#include <queue>
#include <deque>
#include <stack>
#include <vector>
#include <string>
#include <iostream>
#include <algorithm>
#include <iterator>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <cstdio>
#include <bitset>
#include <iomanip>
#define endl '\n'
#define int long long
#define Max(a, b) (((a) > (b)) ? (a) : (b))
#define Min(a, b) (((a) < (b)) ? (a) : (b))
#define BoBoowen ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
using namespace std;
const int inf = 1e9 + 7;
const int N = 2e5 + 10;
map<string, int> mp;
void bfs(string s)
{
string fir = "0000";
queue<string> q;
q.push(fir);
mp[fir] = 0;
while (!q.empty())
{
string now = q.front();
q.pop();
for (int i = 0; i < 4; ++i)
{
int cz = 1;
for (int k = 0; k < 2; ++k)
{
cz *= -1;
string nex = now;
for (int j = 0; j + i < 4; ++j)
{
nex[i + j] = (nex[i + j] - '0' + cz + 10) % 10 + '0';
if (mp[nex] == 0 && nex != fir)
{
// cout << nex << endl;
mp[nex] = mp[now] + 1;
q.push(nex);
}
}
}
}
}
}
void solved()
{
string s1, s2;
cin >> s1 >> s2;
string s;
for (int i = 0; i < 4; ++i)
{
s += (s1[i] - s2[i] + 10) % 10 + '0';
}
// cout << s << endl;
cout << mp[s] << endl;
}
signed main()
{
BoBoowen;
bfs("0000");
int T = 1;
cin >> T;
while (T--)
{
solved();
}
}
小小解读
mp
是一个map
,用来存储每个状态到达该状态的最小步数。bfs
函数负责从初始状态0000
开始进行广度优先搜索,计算到达每个状态的最小步数。q
是一个队列,用来存储待探索的状态。mp
存储每个状态的最小步数。初始时,将状态0000
的步数设为 0。- 对于每个状态,尝试选择一个连续的子序列进行旋转操作。这里旋转有两种方式:顺时针(
cz = 1
)和逆时针(cz = -1
)。 - 每次旋转操作后,如果新的状态未被访问过,就将该状态加入队列并更新步数。
solved
函数负责处理每个测试用例。- 从输入中读取当前状态
s1
和目标状态s2
。 - 计算
s1
和s2
之间的差值,并转换成一个新的状态s
。这个差值表示需要旋转的步骤,(s1[i] - s2[i] + 10) % 10
保证了旋转结果始终在 0 到 9 之间。 - 最后,输出该状态到达目标所需的最小步数,即
mp[s]
。 - 主函数中,先调用
bfs("0000")
函数初始化所有从0000
到其他状态的最小步数。 - 然后通过循环处理每个测试用例。
总结
- BFS 算法:用广度优先搜索遍历所有状态,确保找到从
0000
到目标状态的最小步数。 - 状态转换:每次旋转一个连续的子序列,旋转方式有顺时针和逆时针。
- 时间复杂度:由于
bfs
函数会遍历所有可能的状态,因此时间复杂度为O(10^4)
,由于最多有T
个测试用例,总的时间复杂度大约是O(T * 10^4)
,对于T = 10^5
,每个测试用例的计算时间是常数时间,能够满足题目的要求。