DFS/BFS专训1
文章目录
A. 组合的输出
题目描述
排列与组合是常用的数学方法,其中组合就是从n个元素中抽出r个元素(不分顺序且r≤n),我们可以简单地将n个元素理解为自然数1,2,…,n,从中任取r个数。现要求你用递归的方法输出所有组合。
例如n=5,r=3,所有组合为:
1 2 3 1 2 4 1 2 5 1 3 4 1 3 5 1 4 5 2 3 4 2 3 5 2 4 5 3 4 5
输入格式
一行:两个正整数 n 和 r
输出格式
字典序从小到大的全排列及全排列的总个数。
数据范围 r<n<10
输入样例
3 2
输出样例
1 2
1 3
2 1
2 3
3 1
3 2
6
代码
#include <iostream>
#include <cstdio>
using namespace std;
int k, r, a[20], v[20], cnt;
void dfs(int n) {
if (n == r + 1) {
cnt++;
for (int i = 1; i <= r; i++) {
printf("%d ", a[i]);
}
printf("\n");
} else {
for (int i = 1; i <= k; i++) {
if (v[i] == 0) {
a[n] = i;
v[i] = 1;
dfs(n + 1);
v[i] = 0;
}
}
}
}
int main() {
scanf("%d%d", &k, &r);
dfs(1);
cout << cnt;
}
B.自然数的拆分
题目描述
任何一个大于1的自然数n,总可以拆分成若干个小于n的自然数之和。
当n=7共14种拆分方法:
7=1+1+1+1+1+1+1
7=1+1+1+1+1+2
7=1+1+1+1+3
7=1+1+1+2+2
7=1+1+1+4
7=1+1+2+3
7=1+1+5
7=1+2+2+2
7=1+2+4
7=1+3+3
7=1+6
7=2+2+3
7=2+5
7=3+4
输入格式
一行:两个正整数 n 和 r
输出格式
字典序从小到大的全排列及全排列的总个数。
数据范围
r<n<10
输入样例
7
输出样例
1+1+1+1+1+1+1
1+1+1+1+1+2
1+1+1+1+3
1+1+1+2+2
1+1+1+4
1+1+2+3
1+1+5
1+2+2+2
1+2+4
1+3+3
1+6
2+2+3
2+5
3+4
代码
#include <iostream>
#include <cstdio>
using namespace std;
int k, cnt, a[30];
void pr(int n) {
for (int i = 1; i < n; i++) {
if (i == 1)
printf("%d", a[i]);
else
printf("+%d", a[i]);
}
printf("\n");
}
void dfs(int bx, int sum, int num) {
//bx当前数 sum当前总和 num入选个数->便于输出的参数
if (bx == k)
return;
//当前数等于k时 即bx增长达上限 回溯
if (sum == k) {
pr(num);
cnt++;
return;
}
//剪枝 i不超过 k-当前总和
for (int i = bx; i <= k - sum; i++) {
a[num] = i;
dfs(i, sum + i, num + 1);
}
}
int main() {
scanf("%d", &k);
dfs(1, 0, 1);
// cout<<cnt;
}
C. 马的遍历
题目描述
中国象棋半张棋盘如图所示。马自左下角往右上角跳。今规定只许往右跳,不许往左跳。比如图4(a)中所示为一种跳行路线,并将所经路线打印出来。
输出格式
第一行:0,0–>0,0–>2,1–>3,3–>1,4–>3,5–>2,7–>4,8
第二行:一个整数,表示第几种跳法
样例输出
0,0–>0,0–>2,1–>4,2–>3,4–>4,6–>2,7–>4,8
0,0–>0,0–>2,1–>4,2–>3,4–>1,5–>3,6–>4,8
……
总的跳法数
代码
注意当需要输出路径时 此类题目需用dfs而非bfs
#include<iostream>
#include<cstdio>
using namespace std;
int a[2][15],cnt;
int dx[4]={2,1,-1,-2};
int dy[4]={1,2,2,1};
bool check(int fx,int fy){
if(fx<=4&&fx>=0&&fy<=8&&fy>=0)
return 1;
return 0;
}
void pr(int n){
for(int i=0;i<n;i++){
if(i!=n-1)
printf("%d,%d-->",a[0][i],a[1][i]);
else
printf("%d,%d",a[0][i],a[1][i]);
}printf("\n");
// system("pause");
}
void dfs(int n,int x,int y){
//当前在第n步 坐标x,y
if(x==4&&y==8){
pr(n);cnt++;
}
else{
for(int i=0;i<4;i++){
int fx=x+dx[i];
int fy=y+dy[i];
if(check(fx,fy)){
//fx fy 通过check之后 保存路径
a[0][n]=fx;
a[1][n]=fy;
dfs(n+1,fx,fy);
}
}
}
}
int main(){
//注意初始化
a[0][0]=0;a[1][0]=0;
a[0][1]=0;a[1][0]=0;
dfs(2,0,0);
cout<<cnt;
}
D. N皇后问题
题目描述
在N*N的棋盘上放置N个皇后(n<=10)而彼此不受攻击(即在棋盘的任一行,任一列和任一对角线上不能放置2个皇后),编程求解所有的摆放方法。
输入格式
输入:n
输出格式
每行输出一种方案,每种方案顺序输出皇后所在的列号,每个数占5列。若无方案,则输出no solute!
输出格式:"%5d "
数据范围
n<=8
输入样例
4
输出样例
2 4 1 3
3 1 4 2
代码
经典dfs
#include <iostream>
#include <cstdio>
using namespace std;
int k, a[15], cnt;
bool check(int n) {
for (int i = 1; i < n; i++) {
if (a[n] + n == a[i] + i || a[n] - n == a[i] - i || a[n] == a[i])
return 0;
}
return 1;
}
void dfs(int n) {
if (n == k + 1) {
cnt++;
for (int i = 1; i <= k; i++) {
printf("%5d", a[i]);
}
printf("\n");
} else {
for (int i = 1; i <= k; i++) {
a[n] = i;
if (check(n))
dfs(n + 1);
}
}
}
int main() {
scanf("%d", &k);
dfs(1);
if (cnt == 0)
printf("no solute!");
return 0;
}
E. 有重复元素的排列问题
题目描述
设R={ r1, r2 , …, rn}是要进行排列的n个元素。其中元素r1, r2 , …, rn可能相同。试设计一个算法, 列出R的所有不同排列。
给定n 以及待排列的n 个元素。计算出这n 个元素的所有不同排列。
输入格式
第1 行是元素个数n,1≤n≤500。接下来的1 行是待排列的n个元素。
输出格式
计算出的n个元素的所有不同排列输出到文件perm.out中。文件最后1行中的数是排列总数。
输入样例
4
aacc
输出样例
aacc
acac
acca
caac
caca
ccaa
6
代码
F1 全排列函数用法
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int k, cnt;
char t[510];
string s;
int main() {
scanf("%d", &k);
cin >> s;
for (int i = 1; i <= k; i++) {
t[i] = s[i - 1];
}
sort(t + 1, t + 1 + k);
for (int i = 1; i <= k; i++) cout << t[i];
cout << endl;
cnt++;
while (next_permutation(t + 1, t + 1 + k)) {
for (int i = 1; i <= k; i++) cout << t[i];
cout << endl;
cnt++;
}
cout << cnt;
return 0;
}
F2 极其妙的解法!统计各个字母出现的次数 利用桶排进行dfs
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
string s;
int n, cnt;
int f[30];
char a[1510];
void dfs(int k) {
if (k == n + 1) {
for (int i = 1; i <= n; i++) {
printf("%c", a[i] + 96);
}
printf("\n");
cnt++;
} else {
for (int i = 1; i <= 26; i++) {
if (f[i] > 0) {
a[k] = i;
f[i]--;
dfs(k + 1);
f[i]++;
}
}
}
}
int main() {
scanf("%d", &n);
cin >> s;
for (int i = 1; i <= n; i++) {
f[s[i - 1] - 96]++;
}
dfs(1);
printf("%d", cnt);
}
F. 子集和问题
题目描述
子集和问题的一个实例为〈S,t〉。其中,S={ x1, x2,…, xn}是一个正整数的集合,c是一个正整 数。子集和问题判定是否存在S的一个子集S1,使得子集S1和等于c。
对于给定的正整数的集合S={ x1, x2,…, xn}和正整数c,编程计算S 的一个子集S1,使得子集S1和 等于c。
输入格式
第1行有2个正整数n和c,n表示S的个数,c是子集和的目标值。
接下来的1行中,有n个正整数,表示集合S中的元素。
输出格式
输出子集和问题的解。
当问题无解时,输出“No Solution!”。
数据范围
n<=10000 xi<=1000
输入样例
5 10
2 2 6 5 4
输出样例
2 2 6
代码
dfs回溯法 注意剪枝 数组开大
#include <iostream>
#include <cstdio>
using namespace std;
int n, c, cnt, tot;
int a[15000], t[15000];
bool v[15000];
void print(int k) {
for (int i = 1; i < k; i++) {
printf("%d ", t[i]);
}
}
void dfs(int k, int sum) {
if (sum > c)
return; //剪枝
if (cnt >= 1)
return;
if (sum == c) {
cnt++;
if (cnt == 1)
print(k);
}
else {
for (int i = k; i <= n; i++)
if (v[i] == 0) {
t[k] = a[i];
sum += a[i];
v[i] = 1;
dfs(k + 1, sum);
v[i] = 0;
sum -= a[i];
}
}
}
int main() {
scanf("%d", &n);
scanf("%d", &c);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
tot += a[i];
}
//剪枝
if (tot == c) {
print(n + 1);
return 0;
}
if (tot < c) {
printf("No Solution!");
return 0;
}
dfs(1, 0);
if (cnt == 0)
printf("No Solution!");
}
G. 工作分配问题
题目描述
设有n件工作分配给n个人。将工作i分配给第j个人所需的费用为cij。试设计一个算法,为每一个人都 分配一件不同的工作,并使总费用达到最小。
设计一个算法,对于给定的工作费用,计算最佳工作分配方案,使总费用达到最小。
输入格式
第一行有1个正整数n (1≤n≤20)。接下来的n行,每行n个数,第i行表 示第i个人各项工作费用。
输出格式 输出最小总费用
输入样例
3
4 2 5
2 3 6
3 4 5
输出样例
9
代码
与八皇后问题及其类似 在二维数组中进行搜索
#include <iostream>
#include <cstdio>
#define inf 0x7f7f7f7f
using namespace std;
int k, a[30][30];
int t[30], sum, res = inf;
bool v[30];
void dfs(int n) {
if (n == k + 1) {
if (res > sum) {
res = sum;
return;
}
} else {
for (int i = 1; i <= k; i++) {
if (v[i] == 0) {
t[n] = a[n][i];
v[i] = 1;
sum += a[n][i];
if (sum < res)
dfs(n + 1);
v[i] = 0;
sum -= a[n][i];
}
}
}
}
int main() {
scanf("%d", &k);
for (int i = 1; i <= k; i++) {
for (int j = 1; j <= k; j++) {
scanf("%d", &a[i][j]);
}
}
dfs(1);
printf("%d", res);
}
H. 装载问题
题目描述
有一批共n个集装箱要装上艘载重量为c的轮船,其中集装箱i的重量为wi。找出一种最优装载方案,将 轮船尽可能装满,即在装载体积不受限制的情况下,将尽可能重的集装箱装上轮船。
输入格式
第一行有2个正整数n和c。n是集装箱数,c是轮船的载重量。接下来的1 行中有n个正整数,表示集装箱的重量。
输出格式
将计算出的最大装载重量输出
数据范围
输入样例
5 10
7 2 6 5 4
输出样例
10
代码
硬用dfs做01
#include <iostream>
#include <cstdio>
using namespace std;
int n, c, a[100];
int res;
void dfs(int k, int sum) {
if (k == n + 1) {
res = max(res, sum);
return;
}
if (sum > c)
return;
if (sum == c) {
res = c;
return;
}
else {
//0表示不装 1表示装
for (int i = 0; i < 2; i++) {
if (i == 1) {
if (sum + a[k] <= c)
dfs(k + 1, sum + a[k] );
else
continue;
}
else
dfs(k + 1, sum);
}
}
}
int main() {
scanf("%d%d", &n, &c);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
dfs(1, 0);
printf("%d", res);
}
dp做法 01背包经典模型
#include <iostream>
#include <cstdio>
using namespace std;
int n, V, v[100], res, dp[20005];
int main() {
cin >> n >> V;
for (int i = 1; i <= n; i++) {
cin >> v[i];
}
for (int i = 1; i <= n; i++)
for (int j = V; j >= v[i]; j--) {
dp[j] = max(dp[j], dp[j - v[i]] + v[i]);
}
cout << dp[V];
return 0;
}
I. 字符序列
题目描述
从三个元素的集合[A,B,C]中选取元素生成一个 N 个字符组成的序列,使得没有两个相邻的子序列(子序列长度=2)相同。例:N = 5 时 ABCBA 是合格的,而序列 ABCBC 与 ABABC 是不合格的,因为其中子序列 BC,AB 是相同的。
对于由键盘输入的 N(1<=N<=12),求出满足条件的 N 个字符的所有序列和其总数。
输入格式
N(1<=N<=12)
输出格式
输出满足条件的 N 个字符的所有序列和其总数。
输入样例
4
输出样例
72
代码
#include <iostream>
#include <cstdio>
using namespace std;
int n, a[40], cnt;
bool check(int k) {
if (k < 4)
return 1;
if (a[k - 3] * 10 + a[k - 2] == a[k - 1] * 10 + a[k])
return 0;
return 1;
}
void dfs(int k) {
if (k == n + 1) {
cnt++;
} else {
for (int i = 1; i <= 3; i++) {
a[k] = i;
if (check(k)) {
dfs(k + 1);
}
}
}
}
int main() {
scanf("%d", &n);
dfs(1);
printf("%d", cnt);
}
J. 试卷批分
题目描述
某学校进行了一次英语考试,共有 10 道是非题,每题为 10 分,解答用 1 表示“是”, 用 0 表示“非”的方式。但老师批完卷后,发现漏批了一张试卷,而且标准答案也丢失了, 手头只剩下了 3 张标有分数的试卷。
试卷一:① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩
0 0 1 0 1 0 0 1 0 0
得分:70
试卷二:① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩
0 1 1 1 0 1 0 1 1 1
得分:50
试卷三:① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩
0 1 1 1 0 0 0 1 0 1
得分:30
待批卷:① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩
0 0 1 1 1 0 0 1 1 1
得分:?
输出格式
请编一程序依据这三张试卷,算出漏批的那张试卷的分数。
代码
#include <iostream>
#include <cstdio>
using namespace std;
bool flag;
int a[15], ans;
int t1[15] = { 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0 };
int t2[15] = { 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1 };
int t3[15] = { 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1 };
int t[15] = { 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1 };
bool check() {
int ans1 = 0, ans2 = 0, ans3 = 0;
for (int i = 1; i <= 10; i++) {
if (t1[i] == a[i])
ans1 += 10;
}
if (ans1 != 70)
return 0;
for (int i = 1; i <= 10; i++) {
if (t2[i] == a[i])
ans2 += 10;
}
if (ans2 != 50)
return 0;
for (int i = 1; i <= 10; i++) {
if (t3[i] == a[i])
ans3 += 10;
}
if (ans3 != 30)
return 0;
return 1;
}
void dfs(int n) {
if (n == 11) {
if (check() && flag == 0) {
for (int i = 1; i <= 10; i++) {
if (t[i] == a[i])
ans += 10;
}
cout << ans;
flag = 1;
return;
}
} else {
for (int i = 0; i < 2; i++) {
a[n] = i;
dfs(n + 1);
}
}
}
int main() {
dfs(1);
}
H. 部落卫队
题目描述
原始部落byteland中的居民们为了争夺有限的资源,经常发生冲突。几乎每个居民都有他的仇敌。部落酋长为了组织一支保卫部落的队伍,希望从部落的居民中选出最多的居民入伍,并保证队伍中任何2 个 人都不是仇敌。
给定byteland部落中居民间的仇敌关系,编程计算组成部落卫队的最佳方案。
输入格式
第1行有2个正整数n和m,表示byteland部落中有n个居民,居民间有m个仇敌关系。居民编号为1,2,…, n。
接下来的m行中,每行有2个正整数u和v,表示居民u与居民v是仇敌。
输出格式
第1行是部落卫队的总人数;第2行是卫队组成xi,1≤i≤n,xi =0 表示居民i不在卫队中,xi=1表示居 民i在卫队中。
输入样例
7 10
1 2
1 4
2 4
2 3
2 5
2 6
3 5
3 6
4 5
5 6
输出样例
3
1 0 1 0 0 0 1
代码
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
int n, m, res;
bool ans[250];
bool v[250];
vector<int> q[250];
bool check(int k) {
if (v[k] == 1)
return 0;
for (int i = 1; i < k; i++) {
if (v[i] == 1) {
for (int j = 0; j < q[i].size(); j++) {
if (q[i][j] == k)
return 0;
}
}
}
return 1;
}
void dfs(int k) {
if (k == n + 1) {
int num = 0;
for (int i = 1; i <= n; i++) {
if (v[i] == 1)
num++;
// printf("%d ",v[i]);
}
if (num >= res) {
res = num;
for (int i = 1; i <= n; i++)
ans[i] = v[i];
}
} else {
for (int i = 0; i <= 1; i++) {
if (i == 1) {
if (check(k)) {
v[k] = 1;
dfs(k + 1);
v[k] = 0;
}
} else
dfs(k + 1);
}
}
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
int x, y;
scanf("%d%d", &x, &y);
q[x].push_back(y);
q[y].push_back(x);
}
dfs(1);
printf("%d\n", res);
for (int i = 1; i <= n; i++) {
printf("%d ", ans[i]);
}
}
L. 最佳调度问题
疯狂剪枝
题目描述
假设有n个任务由k个可并行工作的机器完成。完成任务i需要的时间为ti。试设计一个算法找出完成这 n个任务的最佳调度,使得完成全部任务的时间最早。
对任意给定的整数n和k,以及完成任务i需要的时间为ti,i=1~n。编程计算完成这n个任务的最佳调度。
输入格式
第1行有2 个正整数n和k。(1000以内)
第2行的n个正整数是完成n个任务需要的时间。
输出格式
将计算出的完成全部任务的最早时间输出
数据范围
输入样例
7 3
2 14 4 16 6 5 3
输出样例
17
代码
剪枝1:对所有工作进行降序排列 先从时间最长搜起
剪枝2:当机器 i 与机器 j 的工作时间相同时 只用搜一个
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int n, l, a[1010];
int t[1010];
int res = 10000000;
bool cmp(int x, int y) { return x > y; }
void dfs(int k, int tt) {
if (tt >= res)//小剪枝
return;
if (k == n + 1) {
res = tt;
return;
} else {
bool v[1000] = { 0 };
for (int i = 1; i <= l; i++) {
if (v[t[i]])//剪枝2
continue;
v[t[i]] = 1;
t[i] += a[k];
dfs(k + 1, max(tt, t[i]));
t[i] -= a[k];
}
}
}
int main() {
scanf("%d%d", &n, &l);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
sort(a + 1, a + 1 + n, cmp);//剪枝1
dfs(1, 0);
printf("%d", res);
}
M. 图的m着色问题
题目描述
给定无向连通图G和m种不同的颜色。用这些颜色为图G的各顶点着色,每个顶点着一种颜色。如果有一种着色法使G中每条边的2个顶点着不同颜色,则称这个图是m可着色的。图的m着色问题是对于给定图G和m种颜色,找出所有不同的着色法。
对于给定的无向连通图G和m种不同的颜色,编程计算图的所有不同的着色法。
输入格式
第1行有3个正整数n,k 和m,表示给定的图G有n个顶点和k条边,m种颜色。顶点编号为1,2,…,n。
接下来的k行中,每行有2个正整数u,v,表示图G 的一条边(u,v)。
输出格式
一个整数 输出图的着色法数
输入样例
5 8 4
1 2
1 3
1 4
2 3
2 4
2 5
3 4
4 5
输出样例
48
代码
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
int n, t, m, cnt;
int a[100];
bool v[100];
vector<int> q[100];
bool check(int k) {
for (int i = 0; i < q[k].size(); i++) {
if (a[k] == a[q[k][i]])
return 0;
}
return 1;
}
void dfs(int k) {
if (k == n + 1) {
for (int i = 1; i <= n; i++) {
if (check(i) == 0)
return;
}
cnt++;
// for(int j=1;j<=n;j++){
// printf("%d ",a[j]);
// }cout<<endl;
} else {
for (int i = 1; i <= m; i++) {
a[k] = i;
if (check(k)) {
dfs(k + 1);
}
a[k] = 0;
}
}
}
int main() {
scanf("%d%d%d", &n, &t, &m);
for (int i = 1; i <= t; i++) {
int u, v;
scanf("%d%d", &u, &v);
q[u].push_back(v);
q[v].push_back(u);
}
dfs(1);
printf("%d", cnt);
}