为了更好的阅读体检,可以查看我的算法学习博客
在线评测链接:P1298
塔子哥是学校舞台的灯光师,舞台灯有十六种不同的颜色和光效组合,对应十六进制的每个数{ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , A , B , C , D , E , F 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F}。
即将举办的毕业晚会策划把舞台灯光的要求发到了塔子哥这里,但是舞台的灯光不能直接从外部输入,需要用特定方式调整:舞台灯用一个十六进制数串表示,每次可以选择数串中的某个子串,进行一次整体变大或变小的翻转。
举个例子,假设数串" A A B B AABB AABB"选择中间的子串" A B AB AB",变大翻转会变成: “ A B C B ABCB ABCB”,变小翻转会变成:“ A 9 A B A9AB A9AB”。特别的,字符” 0 0 0“变小的翻转会变成字符” F F F“,同理字符” F F F“的变大翻转会变成字符” 0 0 0“,以此循环往复。
塔子哥想知道,给定两个数串 s 1 s1 s1 和 s 2 s2 s2,最少可以用多少次翻转使得 s 1 s1 s1 变成 s 2 s2 s2 。
输入第一行一个整数 T T T ,代表测试用例的组数。( 1 ≤ T 1 \leq T 1≤T ≤ 1000 \leq 1000 ≤1000 )
接下来 T T T 行,每一行两个数串 s 1 s1 s1 和 s 2 s2 s2。( s 1 s1 s1 和 s 2 s2 s2 为十六进制数串,长度相等且小于等于4)
输出 T T T 行,每行一个整数,表示每组 s 1 s1 s1 翻转成 s 2 s2 s2 的最少步数。
输入
5
AABB EEFF
FFF 999
01 AE
AFE B0F
ABCD EEEE
输出
4
6
6
1
4
当 t = 1 e 5 t = 1e5 t=1e5 时该如何做?
本题是一道典型的双向 b f s bfs bfs问题。
正常的单源 b f s bfs bfs,以完全二叉树的情况为例,每个点扩展 2 2 2 次,在 20 20 20 次后需要的搜索空间为 2 20 2^{20} 220,因此层数不会过多,否则空间不够。而如果使用双向 bfs ,每次搜索空间更小的一半,即可将搜索空间大规模降低。
来分析本题的搜索空间,直接考虑最大的情况,即字符串的长度为 4 4 4。如果单独考虑每个字符,最坏情况下,每个字符挪动 8 8 8 次即可到达目标字符,即最多挪动 32 次。为什么呢,这是在单独考虑每个字符挪动到对应字符的情况,如字符 0 0 0 挪动到 9 9 9 ,增大需要 9 9 9 次,而减小只需要 8 8 8 次,故每个字符最多挪动 8 8 8 次即可。
之后每次的状态转移的情况:
可以选择 [ 1 , 4 ] [1, 4] [1,4] 一起挪动, [ 1 , 3 ] [1, 3] [1,3] 或者 [ 2 , 4 ] [2, 4] [2,4] 一起挪动, [ 1 , 2 ] [1,2] [1,2]或者 [ 2 , 3 ] [2,3] [2,3]或者 [ 3 , 4 ] [3,4] [3,4] 一起挪动, [ 1 ] [1] [1]或者 [ 2 ] [2] [2]或者 [ 3 ] [3] [3]或者 [ 4 ] [4] [4]分别挪动。一共有 10 10 10 种情况,因为需要满足 b f s bfs bfs的性质,即每次搜索都是第一次搜索到该状态,故每次只能向上或者向下移动 1 1 1 次。
即一个状态可以转移到 20 20 20 个其他状态。
但是状态总数为: 1 6 4 = 65536 16^4=65536 164=65536,每个状态最多遍历 20 20 20个其他状态,总共遍历次数为 20 ∗ 65536 = 1310720 20*65536=1310720 20∗65536=1310720 。但是每个状态只会在第一次被遍历到的时候作为起始点遍历其他点。故如此总搜索次数最坏就是 1310720 1310720 1310720,加上双向 b f s bfs bfs的优化。但是 T = 1000 T=1000 T=1000,故
理论在 1 e 9 1e9 1e9 左右,较为极限,故这里 C + + C++ C++的实现加了很多 G C C GCC GCC编译优化,由于较为极限,这里暂不提供其他语言的实现,有兴趣的同学可以尝试下。
这题为一道ICPC区域赛真题的化简版,原题来自于:J.Luggage Lock
任意两个状态都可以进行对齐操作:设置起始状态 s 1 s_1 s1转变为全 0 0 0。
举个例子:
s 1 = 11 , s 2 = 35 s_1=11,s_2=35 s1=11,s2=35 , 那么将其转变成 s 1 = 00 , s 2 = 24 s_1 = 00 , s_2 = 24 s1=00,s2=24 , 两者本质没区别。
这样操作之后,我们就可以先预处理出从全0的状态转换成其他状态的答案,然后询问的时候 O ( 1 ) O(1) O(1)的转换 + 查询即可。
这样的解法我们就可以处理 1 e 5 1e5 1e5次询问了。
// 2333佬的代码
#pragma GCC optimize("O3")
#pragma GCC optimize("Ofast")
#pragma GCC optimize("unroll-loops")
#pragma GCC target("avx,avx2,fma")
#include
using namespace std;
const int INF = 0x3f3f3f3f;
void solve() {
string s1, s2;
cin >> s1 >> s2;
// 相等直接判 0
if (s1 == s2) {
cout << "0\n";
return;
}
// 将十六进制转换为对应的数字
auto get = [&](char c) -> int {
if (c >= '0' && c <= '9') return c - '0';
return c - 'A' + 10;
};
int a = 0;
for (char c : s1) {
a = a * 16 + get(c);
}
int b = 0;
for (char c : s2) {
b = b * 16 + get(c);
}
int n = s1.size();
vector<int> p(n + 1);
p[0] = 1;
// 获取第 i 位对应的位权
for (int i = 1; i <= n; i++) {
p[i] = p[i - 1] * 16;
}
// 双向bfs需要两个队列和dist数组
queue<int> q1, q2;
vector<int> d1(p[n], INF), d2(p[n], INF);
d1[a] = 0;
d2[b] = 0;
// 将起点加入q1,终点加入q2
q1.push(a);
q2.push(b);
int ans = -1;
while (!q1.empty() || !q2.empty()) {
auto tr = [&](queue<int> &q, vector<int> &ds, vector<int> &dt) -> int {
int s = q.size();
while (s--) {
// 取出队头
int x = q.front(); q.pop();
int t = x;
array<int, 4> a{};
// 获取当前状态的每个位的值
for (int i = 0; i < n; i++) {
a[i] = t & 15;
t >>= 4;
}
// 定义add函数为给当前状态+1 or -1
auto add = [&](int dif) -> int {
for (int i = 0; i < n; i++) {
t = x;
// [i,j] 这个区间
// 依次为 [0,0],[0,1],[0,2],[0,3]
// [1,1],[1,2],[1,3]
// [2,2],[2,3]
// [3,3]
for (int j = i; j < n; j++) {
t -= p[j] * a[j];
t += p[j] * ((a[j] + dif + 16) & 15);
if (ds[t] == INF) {
ds[t] = ds[x] + 1;
// 如果t这个状态从终点也已经走到了,那么直接返回两者相加的结果,即从起点到终点的步数
if (dt[t] != INF) {
ans = ds[t] + dt[t];
return 1;
}
q.push(t);
}
}
}
return 0;
};
// 尝试+1
if (add(1)) return 1;
// 尝试-1
if (add(-1)) return 1;
}
return 0;
};
// 尝试从起点出发继续更新,如果遇到了一个点,从终点已经到达过,则两者之和就是答案
if (tr(q1, d1, d2)) break;
// 尝试从终点出发继续更新,如果遇到了一个点,从起点已经到达过,则两者之和就是答案
if (tr(q2, d2, d1)) break;
}
cout << ans << "\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);
int t = 1;
cin >> t;
while (t--) {
solve();
}
}
#include
using namespace std;
#define ll long long
#define pii pair<int,int>
#define vi vector<int>
#define fi first
#define se second
const int mod = 1e9+7;
const int maxn = 1e5 +5;
map<string,int> mp;
vector<char> cs = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
unordered_map<char , int> d;
int m = cs.size();
char getNext (char x){
if (x == '9') return 'A';
if (x == 'F') return '0';
return (char)(x + 1);
}
char getPre (char x){
if (x == 'A') return '9';
if (x == '0') return 'F';
return (char)(x - 1);
}
void bfs (int len)
{
queue<pair<string,int>> que;
string start = string(len , '0');
que.push({start,0});
mp[start] = 0;
int cnt = 0;
while(que.size())
{
auto t = que.front();
que.pop();
string s;
int p = t.se;
for(int i = 0;i<len;i++)
{
for(int j = i;j<len;j++)
{
s = t.fi;
for(int k = i;k<=j;k++)
s[k]= getNext(s[k]);
if(mp.count(s)==0)
{
//cout<
mp[s] = p+1;
que.push({s,p+1});
}
s = t.fi;
for(int k = i;k<=j;k++)
s[k]= getPre(s[k]);
if(mp.count(s)==0)
{
//cout<
mp[s] = p+1;
que.push({s,p+1});
}
}
}
}
}
int main()
{
ios::sync_with_stdio(false);
for (int i = 1 ; i <= 4 ; i++) bfs(i);
for (int i = 0 ; i < m ; i++) d[cs[i]] = i;
int T;
cin>>T;
while(T--)
{
string a,b;
cin>>a>>b;
for(int i = 0;i<4;i++)
{
int t = ( d[a[i]] - d[b[i]] + m ) % m;
a[i] = cs[t];
}
cout<<mp[a]<<endl;
}
return 0;
}
有能力的同学可以尝试下其他语言~
题目内容均收集自互联网,如如若此项内容侵犯了原著者的合法权益,可联系我: (CSDN网站注册用户名: 塔子哥学算法) 进行删除。