編輯書稿——UVa 11212 IDA星
阿新 • • 發佈:2020-12-06
題目描述
你有一篇由n(2≤n≤9)個自然段組成的文章,希望將它們排列成1, 2,…, n。可以用Ctrl+X(剪下)和Ctrl+V(貼上)快捷鍵來完成任務。每次可以剪下一段連續的自然段,貼上時按照順序貼上。注意,剪貼簿只有一個,所以不能連續剪下兩次,只能剪下和貼上交替。
例如,為了將{2,4,1,5,3,6}變為升序,可以剪下1將其放到2前,然後剪下3將其放到4前。再如,對於排列{3,4,5,1,2},只需一次剪下和一次貼上即可——將{3,4,5}放在{1,2}後,或者將{1,2}放在{3,4,5}前。
輸入輸出
包含多組輸入,每組第一行是n,第二行是1~n的一個排列,當n為0時輸入結束
輸出每組輸入對應的最少剪下數。
Sample Input
6
2 4 1 5 3 6
5
3 4 5 1 2
0
Sample Output
Case 1: 2
Case 2: 1
思路
9個數的排列有\(9!=362880\)個,對於一個排列,我們需要遍歷所有的剪下貼上可能,你可以剪下任意長度的序列到任意位置,這個數字再乘上排列數就很大了。
使用IDA*
來做這道題目,深度最大為8,最多也就8次剪貼。每次把所有本次可能的剪貼情況遍歷,如果沒有有序的就把遍歷深度加深,也就是在一次剪貼的基礎下再加一次,最後肯定只需要看便利了多深就是答案了。
假設\(h\)時當前後置字元不正確的字元個數,\(d\)為當前深度,\(maxd\)為最大允許的迭代深度,那麼\((maxd-d)*3<h\)
感覺困難的地方就是遞迴遍歷所有的狀態。
程式碼
#include "iostream" #include "cstdio" #include "cstring" #define MAXN 10 using namespace std; int a[MAXN]; int n; int h() { int cnt = 0; for (int i = 0; i < n - 1; i++) if (a[i + 1] != a[i] + 1)cnt++; if (a[n - 1] != n)cnt++; return cnt; } bool is_ordered() { for (int i = 0; i < n; i++)if (a[i] != i + 1)return false; return true; } bool dfs(int d, int maxd) { if (3 * d + h() > 3 * maxd)return false; if (is_ordered()) return true; /* original是原排列的備份,side是未選中的部分,每次選中一部分進行剪下會把原來的文字分成三部分 [before] [selected] [after] side記錄的是before和after中的文字 */ int original[MAXN],side[MAXN]; memcpy(original, a, sizeof(a)); for (int s = 0; s < n; s++) {//列舉起始點 for (int e = s; e < n; e++) {//列舉終止點 int ith=1; for (int k = 0; k < s; k++)side[ith++] = original[k]; for (int k = e + 1; k < n; k++)side[ith++] = original[k]; for (int k = 0; k < ith; k++) {// 枚舉出所有組合 k意思為插到第k個位置 int ith2 = 0; int i; for (i = 1; i <= k; i++)a[ith2++] = side[i]; for (i = s; i <= e; i++)a[ith2++] = original[i]; for (i = k+1; i < ith ;i++)a[ith2++] = side[i]; if (dfs(d + 1, maxd))return true; } } } return false; } int main() { int kcase = 1; while (scanf("%d", &n) != EOF && n != 0) { for (int i = 0; i < n; i++)scanf("%d", &a[i]); for (int maxd = 0; maxd < n; maxd++) { if (dfs(0, maxd)) { printf("Case %d: %d\n", kcase++,maxd); break; } } } return 0; }