算法提高课笔记
原题链接
马在中国象棋以日字形规则移动。
请编写一段程序,给定 n∗m 大小的棋盘,以及马的初始位置 (x,y),要求不能重复经过棋盘上的同一个点,计算马可以有多少途径遍历棋盘上的所有点。
输入格式
第一行为整数 T,表示测试数据组数。
每一组测试数据包含一行,为四个整数,分别为棋盘的大小以及初始位置坐标 n,m,x,y。
输出格式
每组测试数据包含一行,为一个整数,表示马能遍历棋盘的途径总数,若无法遍历棋盘上的所有点则输出 0。
数据范围
1 ≤ T ≤ 9,
1 ≤ m , n ≤9,
1 ≤ n × m ≤ 28,
0 ≤ x ≤ n − 1,
0 ≤ y ≤ m − 1
输入样例
1
5 4 0 0
输出样例
32
给出矩阵大小,给出马的初始位置,马只能走日。问有多少种方案让马可以遍历完棋盘上的所有点,每种方案里不可以重复经过两个点
这就是典型的外部搜索,是不同状态之间的搜索,因此每次需要恢复现场(可以理解为悔棋)
#include
using namespace std;
const int N = 10;
int n, m;
bool st[N][N]; // 判重
int ans;
int dx[8] = {-2, -1, 1, 2, 2, 1, -1, -2};
int dy[8] = {1, 2, 2, 1, -1, -2, -2, -1};
void dfs(int x, int y, int cnt) // 前两个参数表示当前点坐标,第三个参数表示目前已经搜了多少个点
{
if (cnt == n * m)
{
ans ++ ;
return;
}
st[x][y] = true;
for (int i = 0; i < 8; i ++ ) // 遍历八个操作
{
int a = x + dx[i], b = y + dy[i];
if (a < 0 || a >= n || b < 0 || b >= m) continue; // 位置不合法
if (st[a][b]) continue; // 已遍历
dfs(a, b, cnt + 1);
}
st[x][y] = false;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t -- )
{
int x, y;
cin >> n >> m >> x >> y;
memset(st, 0, sizeof st);
ans = 0;
dfs(x, y, 1);
cout << ans << '\n';
}
}
原题链接
单词接龙是一个与我们经常玩的成语接龙相类似的游戏。
现在我们已知一组单词,且给定一个开头的字母,要求出以这个字母开头的最长的“龙”,每个单词最多被使用两次。
在两个单词相连时,其重合部分合为一部分,例如 beast 和 astonish ,如果接成一条龙则变为 beastonish。
我们可以任意选择重合部分的长度,但其长度必须大于等于1,且严格小于两个串的长度,例如 at 和 atide 间不能相连。
输入格式
输入的第一行为一个单独的整数 n 表示单词数,以下 n 行每行有一个单词(只含有大写或小写字母,长度不超过20),输入的最后一行为一个单个字符,表示“龙”开头的字母。
你可以假定以此字母开头的“龙”一定存在。
输出格式
只需输出以此字母开头的最长的“龙”的长度。
数据范围
n ≤ 20,
单词随机生成。
输入样例
5
at
touch
cheat
choose
tact
a
输出样例
23
提示
连成的“龙”为 atoucheatactactouchoose。
给出多个字符串,首位有相同字串的两个字符串可以连接,给出开头字符,问能连接的最大长度
外部搜索,每次需恢复原状
从开头字符与给定字符相同的单词开始,每次遇到能接到字符串后面的就往深遍历,(形成一个搜索树一样的结构)
下方代码有详细注释
#include
using namespace std;
const int N = 21;
int n;
string word[N]; // 记录每个单词
int g[N][N]; // 记录每两个单词有多少重合
int used[N]; // 记录这个单词被用了多少次
int ans;
void dfs(string dragon, int last) // 第一个参数是当前字符串 第二个参数是当前最后一个字符串编号
{
ans = max((int)dragon.size(), ans); // 更新最大长度
used[last] ++ ; // 更新字符串使用次数
for (int i = 0; i < n; i ++ ) // 遍历每个字符串
if (g[last][i] && used[i] < 2) // 条件:和当前最后一个字符串有重合 && 使用次数不到2次
dfs(dragon + word[i].substr(g[last][i]), i);
used[last] -- ; // 恢复
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n;
for (int i = 0; i < n; i ++ ) cin >> word[i];
char start;
cin >> start;
// 计算每两个字符串最短的重合字符个数
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n; j ++ )
{
string a = word[i], b = word[j];
for (int k = 1; k < min(a.size(), b.size()); k ++ ) // 从1开始,最长不超过较短字符串长度
if (a.substr(a.size() - k, k) == b.substr(0, k)) // 一旦重合立刻跳出
{
g[i][j] = k;
break;
}
}
for (int i = 0; i < n; i ++ )
if (word[i][0] == start)
dfs(word[i], i);
cout << ans << '\n';
}
原题链接
给定 n 个正整数,将它们分组,使得每组中任意两个数互质。
至少要分成多少个组?
输入格式
第一行是一个正整数 n。
第二行是 n 个不大于10000的正整数。
输出格式
一个正整数,即最少需要的组数。
数据范围
1 ≤ n ≤ 10
输入样例
6
14 20 33 117 143 175
输出样例
3
每个组的数要互质,给出一系列数,问最少多少组
按照组合的方式搜索,每个组里按照下表从小到大添加所有能加进去的元素,直到不能再加任何一个元素,就新开一个组
当某个数可以加到最后一组时,就没有必要新开一个组
下方代码中有详细注释
#include
using namespace std;
const int N = 10;
int n;
int p[N]; // 存所有元素
int group[N][N]; // 存每个组以及其中元素
bool st[N]; // 判重
int ans = N;
int gcd(int a, int b) // 找最大公约数
{
return b ? gcd(b, a % b) : a;
}
bool check(int group[], int gc, int i)
{
for (int j = 0; j < gc; j ++ ) // 遍历group中的每个元素
if (gcd(p[group[j]], p[i]) > 1) // 最大公约数大于1说明不是互质
return false;
return true;
}
void dfs(int u, int gc, int tc, int start) // u:第几组 gc:当前组内下标 tc:当前一共有多少元素 start:当前这一组从哪个元素开始搜
{
if (u >= ans) return; // 剪枝优化:如果当前组数已经大于等于ans 说明一定不是最优解 直接返回
if (tc == n) ans = u; // 所有数都搜索完了
bool flag = true; // true表示当前组不能继续添加新元素
for (int i = start; i < n; i ++ ) // 从start开始遍历
if (!st[i] && check(group[u], gc, i)) // 该元素没用过且与当前组所有元素互质
{
st[i] = true; // 标记该元素
group[u][gc] = i; // 将该元素加入组中
dfs(u, gc + 1, tc + 1, i + 1); // 下一层遍历
st[i] = false; // 恢复现场
flag = false; // 表示还能添加新元素
}
if (flag) dfs(u + 1, 0, tc, 0); // 不能添加新元素时新开一个组
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n;
for (int i = 0; i < n; i ++ ) cin >> p[i];
dfs(1, 0, 0, 0);
cout << ans << '\n';
}