避免搜索过深 但答案在较浅的位置这种情况的发生
满足如下条件的序列X(序列中元素被标号为1、2、3…m)被称为“加成序列”:
1、 X [ 1 ] = 1 X[1]=1 X[1]=1
2、 X [ m ] = n X[m]=n X[m]=n
3、 X [ 1 ] < X [ 2 ] < … < X [ m − 1 ] < X [ m ] X[1]
4、对于每个 k k k( 2 ≤ k ≤ m 2≤k≤m 2≤k≤m)都存在两个整数 i i i 和 j j j ( 1 ≤ i , j ≤ k − 1 1≤i,j≤k−1 1≤i,j≤k−1, i i i 和 j j j 可相等),使得 X [ k ] = X [ i ] + X [ j ] X[k]=X[i]+X[j] X[k]=X[i]+X[j]。
你的任务是:给定一个整数n,找出符合上述条件的长度m最小的“加成序列”。
如果有多个满足要求的答案,只需要找出任意一个可行解。
输入包含多组测试用例。
每组测试用例占据一行,包含一个整数n。
当输入为单行的0时,表示输入结束。
对于每个测试用例,输出一个满足需求的整数序列,数字之间用空格隔开。
每个输出占一行。
1 ≤ n ≤ 100 1≤n≤100 1≤n≤100
优化搜索顺序:① 优先枚举较大的数
排除等效冗余:① 某两对数和相等时 只需要选择其中一对
#include
using namespace std;
const int N = 110;
int n;
int ans[N];
bool dfs(int u, int depth) {
if (u > depth)return false;
if (ans[u - 1] == n)return true;
bool st[N] = { 0 };
for (int i = u - 1; i >= 0; --i) {
for (int j = i; j >= 0; --j) {
int s = ans[i] + ans[j];
if (st[s] || s > n || s <= ans[u - 1])continue;
st[s] = true;
ans[u] = s;
if (dfs(u + 1, depth))return true;
}
}
return false;
}
int main() {
ans[0] = 1;
while (cin >> n, n) {
int depth = 1;
while (!dfs(1, depth))depth++;
for (int i = 0; i < depth; ++i)printf("%d ", ans[i]);
cout << endl;
}
}
达达帮翰翰给女生送礼物,翰翰一共准备了 N N N个礼物,其中第 i i i个礼物的重量是 G [ i ] G[i] G[i]。
达达的力气很大,他一次可以搬动重量之和不超过 W W W的任意多个物品。
达达希望一次搬掉尽量重的一些物品,请你告诉达达在他的力气范围内一次性能搬动的最大重量是多少。
第一行两个整数,分别代表W和N。
以后N行,每行一个正整数表示G[i]。
仅一个整数,表示达达在他的力气范围内一次性能搬动的最大重量。
1 ≤ N ≤ 46 1≤N≤46 1≤N≤46,
1 ≤ W , G [ i ] ≤ 2 31 − 1 1≤W,G[i]≤2^{31}−1 1≤W,G[i]≤231−1
1.将所有物品按重量从大到小排序
2.先将前 k k k件物品能凑出的重量打表,然后排序判重
3.搜索剩下的 N − K N-K N−K 件物品的选择方式 并从表中二分出不超过 W W W 的最大值
#include
using namespace std;
typedef long long LL;
const int N = 50;
int n, m;
int w[N], weights[1 << 25];
int k, cnt = 1, res;
void dfs1(int u, int s) {
if (u == k) {
weights[cnt++] = s;
return;
}
dfs1(u + 1, s);
if ((LL)s + w[u] <= m)dfs1(u + 1, s + w[u]);
}
void dfs2(int u, int s) {
if (u == n) {
int l = 0, r = n;
while (l < r) {
int mid = l + r + 1 >> 1;
if (weights[mid] <= m - s)l = mid;
else r = mid - 1;
}
res = max(res, weights[l] + s);
return;
}
dfs2(u + 1, s);
if ((LL)s + w[u] <= m)dfs2(u + 1, s + w[u]);
}
int main() {
cin >> m >> n;
for(int i = 1;i <= n;++i){
scanf("%d", &w[i]);
}
sort(w, w + n);
reverse(w, w + n);
int k = n / 2 + 2;
dfs1(1, 0);
cnt = unique(weights, weights + cnt) - weights;
dfs2(k + 1, 0);
cout << res << endl;
return 0;
}
启发式搜索 + 迭代加深
如果估价函数(需要再搜的最小层数) + 当前层数 > 当前迭代加深的层数 在当前迭代肯定无解
迭代扩大搜索的层数 直到最终找到答案 或者无解
给定n本书,编号为1-n。
在初始状态下,书是任意排列的。
在每一次操作中,可以抽取其中连续的一段,再把这段插入到其他某个位置。
我们的目标状态是把书按照1-n的顺序依次排列。
求最少需要多少次操作。
第一行包含整数 T T T,表示共有 T T T组测试数据。
每组数据包含两行,第一行为整数 n n n,表示书的数量。
第二行为 n n n个整数,表示 1 − n 1-n 1−n的一种任意排列。
同行数之间用空格隔开。
每组数据输出一个最少操作次数。
如果最少操作次数大于或等于5次,则输出”5 or more”。
每个结果占一行。
1 ≤ n ≤ 15 1≤n≤15 1≤n≤15
枚举移动串的长度 和插入的位置 一层一层地搜
#include
using namespace std;
typedef long long LL;
const int N = 15;
int n;
int a[N], w[5][N];
int f() {
int cnt = 0;
for (int i = 0; i + 1 < n; ++i)
if (a[i + 1] != a[i] + 1)
++cnt;
return (cnt + 2) / 3;
}
bool dfs(int depth, int max_depth) {
if (depth + f() > max_depth)return false;
if (f() == 0)return true;
for (int len = 1; len <= n; ++len) { //枚举当前移动的长度
for (int l = 0; l + len - 1 < n; ++l) { //枚举截取的起点
int r = l + len - 1; //计算终点
// 把 l 到 r 这一段放到k的后面去
for (int k = r + 1; k < n; ++k) {// 枚举应该插在哪个地方 避免重复 每次都往后放
memcpy(w[depth], a, sizeof a);
int y = l;
for (int x = r + 1; x <= k; ++x,++y)a[y] = w[depth][x];
for (int x = l; x <= r; ++x, ++y)a[y] = w[depth][x];
if (dfs(depth + 1, max_depth))return true;
memcpy(a, w[depth], sizeof a);
}
}
}
return false;
}
int main() {
int t; cin >> t;
while (t--) {
cin >> n;
for (int i = 0; i < n; ++i)scanf("%d", &a[i]);
int depth = 0;
while (depth < 5 && !dfs(0, depth))depth++;
if (depth >= 5)puts("5 or more");
else cout << depth << endl;
}
}
如下图所示,有一个“#”形的棋盘,上面有1,2,3三种数字各8个。
给定8种操作,分别为图中的A~H。
这些操作会按照图中字母和箭头所指明的方向,把一条长为8的序列循环移动1个单位。
例如下图最左边的“#”形棋盘执行操作A后,会变为下图中间的“#”形棋盘,再执行操作C后会变成下图最右边的“#”形棋盘。
给定一个初始状态,请使用最少的操作次数,使“#”形棋盘最中间的8个格子里的数字相同。
输入包含多组测试用例。
每个测试用例占一行,包含24个数字,表示将初始棋盘中的每一个位置的数字,按整体从上到下,同行从左到右的顺序依次列出。
输入样例中的第一个测试用例,对应上图最左边棋盘的初始状态。
当输入只包含一个“0”的行时,表示输入终止。
每个测试用例输出占两行。
第一行包含所有移动步骤,每步移动用大写字母“A~G”中的一个表示,字母之间没有空格,如果不需要移动则输出“No moves needed”。
第二行包含一个整数,表示移动完成后,中间8个格子里的数字。
如果有多种方案,则输出字典序最小的解决方案。
#include
using namespace std;
typedef long long LL;
const int N = 24;
int op[8][7] = { //图表顺序
{0,2,6,11,15,20,22},
{1,3,8,12,17,21,23},
{10,9,8,7,6,5,4},
{19,18,17,16,15,14,13},
{23,21,17,12,8,3,1},
{22,20,15,11,6,2,0},
{13,14,15,16,17,18,19},
{4,5,6,7,8,9,10},
};
int opposite[8] = { 5,4,7,6,1,0,3,2 }; //每个操作对应的逆操作
int center[8] = { 6,7,8,11,12,15,16,17 };
int q[N];
int path[100];
int f() {
int sum[4] = { 0 };
for (int i = 0; i < 8; ++i) {
sum[q[center[i]]]++;
}
int s = 0;
for (int i = 1; i <= 3; ++i) {
s = max(s, sum[i]);
}
return 8 - s;
}
void operate(int x) {
int s = q[op[x][0]];
for (int i = 0; i < 6; ++i) {
q[op[x][i]] = q[op[x][i + 1]];
}
q[op[x][6]] = s;
}
bool dfs(int depth, int max_depth,int last) {
if (depth + f() > max_depth)return false;
if (!f())return true;
for (int i = 0; i < 8; ++i) {
if (opposite[i] != last) {
operate(i);
path[depth] = i;
if (dfs(depth + 1, max_depth,i))return true;
operate(opposite[i]);
}
}
return false;
}
int main() {
while (cin >> q[0], q[0]) {
for (int i = 1; i < 24; ++i)scanf("%d", &q[i]);
int depth = 0;
while (!dfs(0, depth,-1))depth++;
if (!depth)printf("No moves needed");
else {
for (int i = 0; i < depth; ++i)printf("%c", path[i] + 'A');
}
printf("\n%d\n", q[6]);
}
}