1+ #include < bits/stdc++.h>
2+ using namespace std ;
3+
4+ int n; // 铁盘数量
5+ vector<int > target_state; // 目标状态(实际未使用,因为目标就是 [1,2,3,...,n])
6+ int max_depth; // IDA* 当前迭代的最大深度限制
7+
8+ /* *
9+ * 启发式函数:估计从当前状态到目标状态还需要的最少步数
10+ *
11+ * 原理:计算"断点"数量
12+ * - 断点:相邻两个铁盘的编号差不是 1(说明它们在目标状态中不相邻)
13+ * - 每次翻转最多能消除 2 个断点
14+ * - 因此:所需步数 ≥ ceil(断点数 / 2)
15+ *
16+ * 例如:[3, 1, 2, 4]
17+ * - 3 和 1 之间:|3-1|=2 ≠ 1 → 断点
18+ * - 1 和 2 之间:|1-2|=1 = 1 → 不是断点
19+ * - 2 和 4 之间:|2-4|=2 ≠ 1 → 断点
20+ * - 4 不在最后位置(应该在最后) → 断点
21+ * 总共 3 个断点,估计至少需要 ceil(3/2) = 2 步
22+ */
23+ int heuristic (const vector<int >& state) {
24+ int breakpoints = 0 ;
25+
26+ // 检查最大值(编号 n)是否在最底部(数组末尾)
27+ // 如果不在,说明最大值还需要移动,算一个断点
28+ if (state.back () != n) {
29+ breakpoints++;
30+ }
31+
32+ // 遍历相邻元素,检查它们的编号是否连续
33+ for (int i = 0 ; i < n - 1 ; ++i) {
34+ // 如果相邻元素编号差不是 1,说明它们在目标状态中不相邻
35+ // 需要通过翻转来调整,算作一个断点
36+ if (abs (state[i] - state[i + 1 ]) != 1 ) {
37+ breakpoints++;
38+ }
39+ }
40+
41+ return breakpoints;
42+ }
43+
44+ /* *
45+ * 翻转函数:翻转数组的前 k 个元素
46+ *
47+ * @param arr 要翻转的数组(按值传递,不修改原数组)
48+ * @param k 翻转前 k 个元素
49+ * @return 翻转后的新数组
50+ *
51+ * 例如:flip([3, 1, 2, 4], 3) → [2, 1, 3, 4]
52+ */
53+ vector<int > flip (vector<int > arr, int k) {
54+ reverse (arr.begin (), arr.begin () + k);
55+ return arr;
56+ }
57+
58+ /* *
59+ * 深度优先搜索(DFS)+ 剪枝
60+ *
61+ * IDA* 核心:在限定深度内搜索解
62+ * 剪枝条件:g + h(state) > max_depth,即"已用步数 + 估计剩余步数 > 深度限制"
63+ *
64+ * @param state 当前状态(铁盘排列)
65+ * @param g 从起点到当前状态已经走了多少步(实际代价)
66+ * @param prev_flip 上一次翻转的位置 k(避免重复翻转,如 翻k → 翻k → 回到原状态)
67+ * @return 是否在当前深度限制内找到解
68+ */
69+ bool dfs (const vector<int >& state, int g, int prev_flip) {
70+ // 计算启发式值:估计还需要多少步
71+ int h = heuristic (state);
72+
73+ // 剪枝:如果"已用步数 + 估计步数"超过深度限制,放弃这条路径
74+ // 这是 IDA* 的核心:用启发式函数来剪枝,避免搜索过深
75+ if (g + h > max_depth) {
76+ return false ;
77+ }
78+
79+ // 目标检测:如果启发式值为 0,说明已经达到目标状态 [1,2,3,...,n]
80+ // (没有断点 = 所有相邻元素都连续 = 已排序)
81+ if (h == 0 ) {
82+ return true ; // 找到解!
83+ }
84+
85+ // 尝试所有可能的翻转操作
86+ // k=2: 翻转前 2 个元素
87+ // k=3: 翻转前 3 个元素
88+ // ...
89+ // k=n: 翻转整个数组
90+ for (int k = 2 ; k <= n; ++k) {
91+ // 剪枝优化:避免连续两次翻转同一个位置
92+ // 因为 flip(k) → flip(k) 会抵消,回到原状态,是无效操作
93+ if (k == prev_flip) continue ;
94+
95+ // 生成新状态:执行翻转操作
96+ vector<int > next_state = flip (state, k);
97+
98+ // 递归搜索:在新状态继续搜索
99+ // g+1: 步数加 1
100+ // k: 记录这次翻转的位置,下次避免重复翻转
101+ if (dfs (next_state, g + 1 , k)) {
102+ return true ; // 如果找到解,向上返回
103+ }
104+ }
105+
106+ // 所有可能都试过了,没找到解
107+ return false ;
108+ }
109+
110+ int main () {
111+ ios::sync_with_stdio (false );
112+ cin.tie (NULL );
113+
114+ // 读取输入
115+ cin >> n;
116+ vector<int > start_state_raw (n); // 原始输入(实际半径值)
117+ vector<int > sorted_unique (n); // 用于排序和离散化
118+
119+ for (int i = 0 ; i < n; ++i) {
120+ cin >> start_state_raw[i];
121+ sorted_unique[i] = start_state_raw[i];
122+ }
123+
124+ // ========== 离散化处理 ==========
125+ // 为什么需要离散化?
126+ // 原始输入可能是:[87, 75, 19, 49, 46, ...](实际半径值)
127+ // 但我们只关心相对大小关系,不关心具体数值
128+ // 离散化后:将它们映射为 1, 2, 3, ..., n(排名)
129+ // 这样目标状态就是固定的 [1, 2, 3, ..., n],便于判断和启发
130+
131+ // 第一步:排序,得到从小到大的顺序
132+ sort (sorted_unique.begin (), sorted_unique.end ());
133+ // 例如:[87, 75, 19, ...] → 排序后 → [1, 5, 11, 19, ...]
134+
135+ // 第二步:建立映射表(原始值 → 排名)
136+ map<int , int > val_to_rank;
137+ for (int i = 0 ; i < n; ++i) {
138+ val_to_rank[sorted_unique[i]] = i + 1 ;
139+ // 最小的值 → 排名 1
140+ // 次小的值 → 排名 2
141+ // ...
142+ // 最大的值 → 排名 n
143+ }
144+
145+ // 第三步:将原始状态转换为排名表示
146+ vector<int > start_state (n);
147+ for (int i = 0 ; i < n; ++i) {
148+ start_state[i] = val_to_rank[start_state_raw[i]];
149+ }
150+ // 例如:[87, 75, 19, ...] → [14, 13, 3, ...] (根据大小排名)
151+
152+ // ========== IDA* 迭代加深搜索 ==========
153+ // 什么是 IDA*?
154+ // - 结合了迭代加深(ID)和 A* 搜索
155+ // - 从深度 d=0 开始,如果找不到解,增加到 d=1, d=2, ...
156+ // - 每次深度限制内,用 DFS + 启发式剪枝搜索
157+ // - 优点:空间复杂度低(DFS),时间上有启发式加速
158+
159+ // 从启发式估计值开始迭代
160+ // 初始 max_depth = h(start_state):至少需要这么多步
161+ for (max_depth = heuristic (start_state); ; ++max_depth) {
162+ // 在深度限制 max_depth 内搜索
163+ // 参数:初始状态,已用步数 0,上一次翻转位置 0(表示没有)
164+ if (dfs (start_state, 0 , 0 )) {
165+ // 找到解!输出深度(即最少翻转次数)
166+ cout << max_depth << endl;
167+ break ;
168+ }
169+ // 如果在当前深度找不到,增加深度继续搜索
170+ // max_depth++ 会在下一次循环执行
171+ }
172+
173+ return 0 ;
174+ }
0 commit comments