排书

排书

给定 $n$ 本书,编号为 $1 \sim n$。

在初始状态下,书是任意排列的。

在每一次操作中,可以抽取其中连续的一段,再把这段插入到其他某个位置。

我们的目标状态是把书按照 $1 \sim n$ 的顺序依次排列。

求最少需要多少次操作。

输入格式

第一行包含整数 $T$,表示共有 $T$ 组测试数据。

每组数据包含两行,第一行为整数 $n$,表示书的数量。

第二行为 $n$ 个整数,表示 $1 \sim n$ 的一种任意排列。

同行数之间用空格隔开。

输出格式

每组数据输出一个最少操作次数。

如果最少操作次数大于或等于 $5$ 次,则输出 5 or more 。

每个结果占一行。

数据范围

$1 \leq n \leq 15$

输入样例:

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

 

解题思路

  每次要搜下一层的时候,先枚举连续的一段数,然后把这段连续的数插到剩余的数的各个空隙中。

  比如,如果当前连续的一段数的长度为$i$,假设序列的长度为$n$,那么长度$i$的连续序列有$n-i+1$种。同时剩余的数的空隙有$n - 1$个(这段连续序列原本所在的空隙不算)。因此对于一段长度为$i$的连续序列一共有$(n-i+1) \times (n - i)$种选择。而$i$的取值范围为$1 \leq i < n$,因此在搜索当前层时一共有$\sum\limits_{i=1}^{n-1} {(n-i+1) \times (n - 1)}$种选择。其中这些选择中是有重复的,比如下图:

  为了避免枚举到重复的情况,可以每次都只把数插到当前位置的后面的空隙,而不再插到前面的空隙。因此实际上一共有$$\frac{\sum\limits_{i=1}^{n-1} {(n-i+1) \times (n - 1)}}{2} = \frac{n \times (n+1) \times (n+2)}{6}$$

  代入$n=15$,最多有$560$种选择。又因为最多枚举$4$层,因此一共有${560}^4$种选择。

  这里可以用IDA*,A*,双向bfs来优化。

  对于估价函数,首先要满足当前状态的估价值不大于实际步数。然后对于任意一个序列,每次移动一段连续的数都会改变三个位置的后继:

  因此扫描一遍当前序列,统计前后两个数不满足后继关系(即$1$得后面是$2$,以此类推)的对数,假设有$tot$对,因为每次移动一段数可以改变三个后继,因此最好的情况是交换$\left\lceil {\frac{tot}{3}} \right\rceil = \left\lfloor {\frac{tot+2}{3}} \right\rfloor$次,就可以使得整个序列递增。因此估价函数可以是$f(s) = \left\lceil {\frac{tot}{3}} \right\rceil$。

  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/

posted @ 2022-08-09 23:55  onlyblues  阅读(39)  评论(0编辑  收藏  举报
Web Analytics