【备战秋招】每日一题:2023.05.15-拼多多OD(第三题)-多多的字符翻转

为了更好的阅读体检,可以查看我的算法学习博客
在线评测链接: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 0123456789ABCDEF}。

即将举办的毕业晚会策划把舞台灯光的要求发到了塔子哥这里,但是舞台的灯光不能直接从外部输入,需要用特定方式调整:舞台灯用一个十六进制数串表示,每次可以选择数串中的某个子串,进行一次整体变大或变小的翻转。

举个例子,假设数串" 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 1T ≤ 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 时该如何做?

思路

解法1:双向bfs

本题是一道典型的双向 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 2065536=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编译优化,由于较为极限,这里暂不提供其他语言的实现,有兴趣的同学可以尝试下。

解法2:预处理

这题为一道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次询问了。

类似题目推荐

LeetCode

  1. 127. 单词接龙,双向bfs模板
  2. 752. 打开转盘锁,双向bfs

代码

CPP

解法1
// 2333佬的代码
#pragma GCC optimize("O3")
#pragma GCC optimize("Ofast")
#pragma GCC optimize("unroll-loops")
#pragma GCC target("avx,avx2,fma")

#include <bits/stdc++.h>

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();
    }
}
解法2
#include <bits/stdc++.h>
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<<s<<endl;
                    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<<s<<endl;
                    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网站注册用户名: 塔子哥学算法) 进行删除。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

塔子哥学算法

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值