排书
排书
给定 本书,编号为 。
在初始状态下,书是任意排列的。
在每一次操作中,可以抽取其中连续的一段,再把这段插入到其他某个位置。
我们的目标状态是把书按照 的顺序依次排列。
求最少需要多少次操作。
输入格式
第一行包含整数 ,表示共有 组测试数据。
每组数据包含两行,第一行为整数 ,表示书的数量。
第二行为 个整数,表示 的一种任意排列。
同行数之间用空格隔开。
输出格式
每组数据输出一个最少操作次数。
如果最少操作次数大于或等于 次,则输出 5 or more 。
每个结果占一行。
数据范围
输入样例:
3 6 1 3 4 6 2 5 5 5 4 3 2 1 10 6 8 5 3 4 7 2 9 1 10
输出样例:
2 3 5 or more
解题思路
每次要搜下一层的时候,先枚举连续的一段数,然后把这段连续的数插到剩余的数的各个空隙中。
比如,如果当前连续的一段数的长度为,假设序列的长度为,那么长度的连续序列有种。同时剩余的数的空隙有个(这段连续序列原本所在的空隙不算)。因此对于一段长度为的连续序列一共有种选择。而的取值范围为,因此在搜索当前层时一共有种选择。其中这些选择中是有重复的,比如下图:
为了避免枚举到重复的情况,可以每次都只把数插到当前位置的后面的空隙,而不再插到前面的空隙。因此实际上一共有
代入,最多有种选择。又因为最多枚举层,因此一共有种选择。
这里可以用IDA*,A*,双向bfs来优化。
对于估价函数,首先要满足当前状态的估价值不大于实际步数。然后对于任意一个序列,每次移动一段连续的数都会改变三个位置的后继:
因此扫描一遍当前序列,统计前后两个数不满足后继关系(即得后面是,以此类推)的对数,假设有对,因为每次移动一段数可以改变三个后继,因此最好的情况是交换次,就可以使得整个序列递增。因此估价函数可以是。
IDA*,AC代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 20; 5 6 int n; 7 int a[N]; 8 9 int f() { 10 int cnt = 0; 11 for (int i = 0; i < n - 1; i++) { 12 if (a[i] + 1 != a[i + 1]) cnt++; 13 } 14 15 return (cnt + 2) / 3; 16 } 17 18 bool dfs(int u, int dep) { 19 if (f() + u > dep) return false; // 剪支,如果估价函数得到的交换次数加上当前层数超过dep,那么不用往下搜 20 if (f() == 0) return true; // 当前序列递增,表面找到解 21 22 int backup[N]; 23 memcpy(backup, a, sizeof(a)); 24 for (int len = 1; len < n; len++) { // 枚举连续一段数的长度 25 for (int i = 0; i + len - 1 < n; i++) { // 枚举左端点 26 int j = i + len - 1; // 右端点 27 for (int k = j + 1; k < n; k++) { // 枚举插到哪个数的后面 28 // 交换s[i ~ j]和s[j+1 ~ k]这两段数 29 // 对区间s[i ~ k]进行处理 30 int l = i; 31 32 // 先用s[j+1 ~ k]覆盖s[i ~ i+k-(j+1)] 33 for (int u = j + 1; u <= k; u++) { 34 a[l++] = backup[u]; 35 } 36 37 // 此时l = i+k-(j+1)+1 38 // 再用s[i ~ j]覆盖s[l ~ k] 39 for (int u = i; u <= j; u++) { 40 a[l++] = backup[u]; 41 } 42 43 if (dfs(u + 1, dep)) return true; 44 memcpy(a, backup, sizeof(backup)); 45 } 46 } 47 } 48 49 return false; 50 } 51 52 int main() { 53 int tot; 54 scanf("%d", &tot); 55 while (tot--) { 56 scanf("%d", &n); 57 for (int i = 0; i < n; i++) { 58 scanf("%d", a + i); 59 } 60 61 // 迭代加深 62 int dep = 0; 63 while (dep < 5 && !dfs(0, dep)) { 64 dep++; 65 } 66 67 if (dep >= 5) printf("5 or more\n"); 68 else printf("%d\n", dep); 69 } 70 71 return 0; 72 }
A*,AC代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 typedef pair<int, string> PIS; 5 6 const int N = 20; 7 8 int n; 9 string beg, ed; 10 11 int f(string &s) { 12 int cnt = 0; 13 for (int i = 0; i < n - 1; i++) { 14 if (s[i] + 1 != s[i + 1]) cnt++; 15 } 16 17 return (cnt + 2) / 3; 18 } 19 20 int astar() { 21 if (beg == ed) return 0; 22 23 priority_queue<PIS, vector<PIS>, greater<PIS>> pq; 24 pq.push({f(beg), beg}); 25 unordered_map<string, int> dist; 26 dist[beg] = 0; 27 28 while (!pq.empty()) { 29 string t = pq.top().second; 30 int d = pq.top().first; 31 pq.pop(); 32 33 if (d > 4) continue; // 估价变换到升序序列需要的最小步数超过4,则continue 34 if (t == ed) return dist[ed]; 35 36 for (int len = 1; len < n; len++) { 37 for (int i = 0; i + len - 1 < n; i++) { 38 int j = i + len - 1; 39 for (int k = j + 1; k < n; k++) { 40 string s = t.substr(0, i) + t.substr(j + 1, k - j) + t.substr(i, len) + t.substr(k + 1); 41 if (!dist.count(s) || dist[s] > dist[t] + 1) { 42 dist[s] = dist[t] + 1; 43 pq.push({dist[s] + f(s), s}); 44 } 45 } 46 } 47 } 48 } 49 50 return 5; 51 } 52 53 int main() { 54 int tot; 55 scanf("%d", &tot); 56 while (tot--) { 57 scanf("%d", &n); 58 59 beg.clear(), ed.clear(); 60 for (int i = 0; i < n; i++) { 61 int v; 62 scanf("%d", &v); 63 beg += (char)(v + '0'); 64 ed += (char)(i + '1'); 65 } 66 67 int ret = astar(); 68 69 if (ret >= 5) printf("5 or more\n"); 70 else printf("%d\n", ret); 71 } 72 73 return 0; 74 }
双向bfs,AC代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 20; 5 6 int n; 7 string beg, ed; 8 9 int f(string &s) { 10 int cnt = 0; 11 for (int i = 0; i < n - 1; i++) { 12 if (s[i] + 1 != s[i + 1]) cnt++; 13 } 14 15 return (cnt + 2) / 3; 16 } 17 18 int extend(queue<string> &q, unordered_map<string, int> &d1, unordered_map<string, int> &d2) { 19 int d = d1[q.front()]; // 记得把当前层数的所有节点枚举完 20 while (!q.empty() && d1[q.front()] == d) { 21 string t = q.front(); 22 q.pop(); 23 24 if (d1[t] + f(t) > 4) continue; // 这个优化很重要,不然会tle 25 26 for (int len = 1; len < n; len++) { 27 for (int i = 0; i + len - 1 < n; i++) { 28 int j = i + len - 1; 29 for (int k = j + 1; k < n; k++) { 30 string s = t.substr(0, i) + t.substr(j + 1, k - j) + t.substr(i, len) + t.substr(k + 1); 31 if (d2.count(s)) return d1[t] + 1 + d2[s]; 32 if (d1.count(s)) continue; 33 d1[s] = d1[t] + 1; 34 q.push(s); 35 } 36 } 37 } 38 } 39 40 return 5; 41 } 42 43 int bfs() { 44 if (beg == ed) return 0; 45 46 queue<string> q1, q2; 47 q1.push(beg), q2.push(ed); 48 unordered_map<string, int> d1, d2; 49 d1[beg] = d2[ed] = 0; 50 51 int cnt = 0; 52 while (!q1.empty() && !q2.empty()) { 53 int t; 54 if (q1.size() <= q2.size()) t = extend(q1, d1, d2); 55 else t = extend(q2, d2, d1); 56 if (t < 5) return t; 57 if (++cnt == 4) return 5; // 搜索层数要不超过4 58 } 59 60 return 5; 61 } 62 63 int main() { 64 int tot; 65 scanf("%d", &tot); 66 while (tot--) { 67 scanf("%d", &n); 68 69 beg.clear(), ed.clear(); 70 for (int i = 0; i < n; i++) { 71 int v; 72 scanf("%d", &v); 73 beg += (char)(v + '0'); 74 ed += (char)(i + '1'); 75 } 76 77 int ret = bfs(); 78 79 if (ret >= 5) printf("5 or more\n"); 80 else printf("%d\n", ret); 81 } 82 83 return 0; 84 }
参考资料
AcWing 180. 排书(算法提高班):https://www.acwing.com/video/485/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/16570675.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效