booldfs(int u, int depth){ if (u == depth) return path[u - 1] == n; // if (u > depth) return false; // if (path[u - 1] == n) return true; for (int i = u - 1; i >= 0; -- i) for (int j = i; j >= 0; -- j){ int v = path[i] + path[j]; // 可行性剪枝:由于序列一定是严格单调递增的,所以当前节点值V_u大于目标值n,或小于等于前一节点值V_{u-1}时,直接剪枝 if (v > n || v <= path[u - 1] || st[v]) continue; // 剪枝之排除等效冗余:不同i和j,X[i]+X[j]可能相等,枚举的组合出现实质冗余,需要判重 st[v] = true; path[u] = v; if (dfs(u + 1, depth)) returntrue; // 恢复现场 st[v] = false; } returnfalse; }
intmain(){ while (scanf("%d", &n), n){ // 初始化 memset(st, 0, sizeof st); path[0] = 1; // 起始节点一定是1 // 设置迭代加深的最大深度depth int depth = 1; // 每次搜索失败则加大搜索深度 while (!dfs(1, depth)) depth ++; for (int i = 0; i < depth; ++ i) printf("%d ", path[i]); printf("\n"); } return0; }
constint N = 1 << 25; // 前半部分物品个数k最大是25, 因此最多可能有2^25种方案
// n表示物品总个数,m表示重量上限,k表示前半部分物品个数 int n, m, k; int g[50]; // 存储所有物品的重量 int w[N]; // w存储能凑出来的所有组合重量 int cnt; // cnt记录前部分所有组合重量的个数(需去重) int ans; // 用ans来记录一个全局最大值
// u表示当前枚举到哪个数了,s表示当前重量的和 voiddfs_s(int u, int s){ // 打表 // 如果我们当前已经枚举完第k个数(下标从0开始的)了, // 就把当前的s加到w数组中去,进行打表 if (u == k){ w[cnt ++] = s; // 记录重量 return; } // 枚举当前不选这个物品 dfs_s(u + 1, s); // 枚举选这个物品, 做一个可行性剪枝 //计算和的时候转成long long防止溢出 if ((LL)s + g[u] <= m) { dfs_s(u + 1, s + g[u]); } }
// u表示当前枚举到哪个数了,s表示当前重量的和 voiddfs_e(int u, int s){ // 二分匹配 // 如果已经找完了n个节点,就得到了后半部分当前的重量组合 // 那么需要通过二分进行前后部分的匹配 if (u == n){ int l = 0, r = cnt - 1; while (l < r){ int mid = (l + r + 1) >> 1; // if (w[mid] + (LL)s <= m) if (w[mid] <= m - s) l = mid; else r = mid - 1; } // 这里需主要由于l从0开始,而w[0] = 0,所以可以解决s恰等于n的情况(只从后半部分选),因而不同单独判断 // 且由于前面递归时始终保证了s <= m(否则不会选择物品进行下一层的递归),所以二分时是一定能找到可行的数的(最极端就是取w[0]=0),因而循环结束后w[l] + (LL)s <= m一定成立 ans = max(ans, w[l] + s); return; } // 不选择当前这个物品 dfs_e(u + 1, s); // 选择当前这个物品 // 虽然m,s,g[u]在int范围内,但s + g[u]可能会爆int,要转成LL if ((LL)s + g[u] <= m) dfs_e(u + 1, s + g[u]); }
intmain(){ scanf("%d%d", &m, &n); for (int i = 0; i < n; ++ i) scanf("%d", &g[i]); // 剪枝之优化搜索顺序,重量从大到小排序后再分组、枚举 sort(g, g + n, greater<int>()); // reverse(g, g + n); // 枚举前k个物品的重量组合并打一个表 k = n >> 1; // 或者k = n / 2; 需注意防止 n = 1时,出现死循环 // min((n >> 1) + 2, n - 1)这种策略实际发现并不可行 dfs_s(0, 0); // 做完之后,把w数组从小到大排序,从而方便二分 sort(w, w + cnt); // 判重,只取不重复的 // int t = 1; // for (int i = 1; i < cnt; ++ i) // if (w[i] != w[i - 1]) // w[t ++] = w[i]; // cnt = t; // 去重后尾地址减首地址得到个数 // unique函数可以删除有序数组中的重复元素。 // 注意: // a. 这里的删除不是真的delete,而是将重复的元素放到容器末尾 // b. unique函数的返回值是去重之后的尾地址(去重后最后一个不重复元素后的元素的地址) // c. 一定要先对数组进行排序才可以使用unique函数 cnt = unique(w, w + cnt) - w; // 这里有一点需要格外注意,由于w的空间是大于需要值的,所以数组中实际上含有0 // 而因此在从小到大排序并去重后,w[0] = 0并不表示物品的重量 // 从k开始后半部分的枚举, 当前的和是0 dfs_e(k, 0); printf("%d\n", ans); return0; }
int n; int q[N]; // 书的编号 int w[5][N]; // 用于存储操作前当前的序列,恢复现场使用 // 这里需要理解为什么需要开5个,因为每层递归都需要恢复现场,且浅层要在深层后恢复 // 而最多五层,所以开5个
// 估价函数 intf(){ int tot = 0; // 统计错误后继个数 for (int i = 0; i + 1 < n; ++ i) if (q[i + 1] != q[i] + 1) tot ++; return (tot + 2) / 3; // 最小步数 }
// 检查序列是否已经有序,即检查所有后继关系是否正确 boolcheck(){ for (int i = 0; i + 1 < n; ++ i) if (q[i + 1] != q[i] + 1) returnfalse; returntrue; }
// u: 当前迭代深度; depth: 迭代加深最大深度 booldfs(int u, int depth){ // IDA*剪枝 // 若当前深度+未来估计步数>深度限制,则立即从当前分支回溯 if (u + f() > depth) returnfalse; if (u == depth && check()) returntrue; // 这里不要判断 // 先枚举长度len for (int len = 1; len <= n; ++ len) // 然后枚举移动前左端点l for (int l = 0; l + len - 1 < n; ++ l){ int r = l + len - 1; // 减1别忘了 // 再枚举后移后插入的右端点k, // 剪枝之排除等效冗余,只考虑后移操作 for (int k = r + 1; k < n; ++ k){ memcpy(w[u], q, sizeof q); // 拷贝当前序列,用于恢复现场 // 后移操作需要两步,首先将移动段落右端点后到后移插入的起始端点中间的部分前移 // 然后再将移动段落插入 int x, y; // x使用记录移入数的位置,y始终记录要修改位置 // 将当前移动段落后面的序列前移,即将上图中绿色部分移动到红色部分 // 注意k是插入的右端点 for (x = r + 1, y = l; x <= k; x ++, y ++) q[y] = w[u][x]; // 移动段落插入 for (x = l; x <= r; x ++, y ++) q[y] = w[u][x]; if (dfs(u + 1, depth)) returntrue; // 恢复现场 memcpy(q, w[u], sizeof q); } } // 另一种枚举防止,直接枚举左端点,右端点,插入点(插入的右端点),更简单 // for (int l = 0; l < n; l ++ ) // for (int r = l; r < n; r ++ ) // for (int k = r + 1; k < n; k ++ ){ // memcpy(w[u], q, sizeof q); // int x, y; // for (x = r + 1, y = l; x <= k; x ++, y ++ ) q[y] = w[u][x]; // for (x = l; x <= r; x ++, y ++ ) q[y] = w[u][x]; // if (dfs(u + 1, depth)) return true; // memcpy(q, w[u], sizeof q); // } returnfalse; }
intmain(){ int t; scanf("%d", &t); while(t --){ scanf("%d", &n); for (int i = 0; i < n; ++ i) scanf("%d", &q[i]); int depth = 0; // 注意depth要从0开始,以处理原本就有序的特殊情况 while (depth < 5 && !dfs(0, depth)) depth ++; if (depth >= 5) puts("5 or more"); else // 由于我们使用了迭代加深,所以depth恰好等于最少需要操作次数 printf("%d\n", depth); } return0; }
// 估价函数 intf(){ // 由于只有三种数字1,2,3,所以可以统计每个数字的次数,然后取最大值 int s[4] ={0}; // 注意初始化为0 // static int sum[4]; // 静态数组,不用每次重开浪费空间时间 // memset(sum, 0, sizeof sum); // 需要每次memset重新赋值 for (int i = 0; i < 8; ++ i) s[q[center[i]]] ++; // 计算1 2 3出现次数 int maxv = 0; for (int i = 1; i < 4; ++ i) maxv = max(maxv, s[i]); // 统计出现最多的次数,不用管具体是哪个数字 return8 - maxv; }
// 进行对应的操作 voidoperate(int x){ // 操作都有共性,最后都是将首部添加到尾部 int t = q[op[x][0]]; // 取出头元素 for (int i = 0; i < 6; ++ i) q[op[x][i]] = q[op[x][i + 1]]; // 其他元素前移 // 首部添加到尾部 q[op[x][6]] = t; }
// u: 当前迭代深度, depth: 迭代加深最大深度, last: 上一次操作 booldfs(int u, int depth, int last){ // IDA*剪枝 // 若当前深度+未来估计步数>深度限制,则立即从当前分支回溯 if (u + f() > depth) returnfalse; // 估价函数为0,表明格子内数全部相同 if (!f()) returntrue; // 枚举八个操作 for (int i = 0; i < 8; ++ i) // 剪枝之排除等效冗余,避免逆操作相互抵消 if (opposite[i] != last){ // 由于初始时last=-1,所以不使用oppsite[last] != i的写法 operate(i); path[u] = i; // 深度恰好对应操作数 if (dfs(u + 1, depth, i)) returntrue; // 回溯恢复现场 operate(opposite[i]); } returnfalse; }
intmain(){ while (scanf("%d", &q[0]), q[0]){ for (int i = 1; i < N; ++ i) scanf("%d", &q[i]); int depth = 0; // 注意depth要从0开始,以处理原本就满足要求的特殊情况 while (!dfs(0, depth, -1)) depth ++;
if (!depth) printf("No moves needed"); else{ // 迭代加深的depth能够记录最小步长 for (int i = 0; i < depth; ++ i) printf("%c", 'A' + path[i]); } printf("\n%d\n", q[6]); } return0; }