【集训】DFS/BFS专训1

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);
}

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值