分考场
问题描述
n个人参加某项特殊考试。
为了公平,要求任何两个认识的人不能分在同一个考场。
求是少需要分几个考场才能满足条件。
输入格式
第一行,一个整数n(1<n<100),表示参加考试的人数。
第二行,一个整数m,表示接下来有m行数据
以下m行每行的格式为:两个整数a,b,用空格分开 (1<=a,b<=n) 表示第a个人与第b个人认识。
输出格式
一行一个整数,表示最少分几个考场。
样例输入
5
8
1 2
1 3
1 4
2 3
2 4
2 5
3 4
4 5
样例输出
4
样例输入
5
10
1 2
1 3
1 4
1 5
2 3
2 4
2 5
3 4
3 5
4 5
样例输出
5
特别感谢
在进行讲解之前,首先要特别感谢以下两位博主的相关文章,给我提供了很多思路,同时也学到了很多知识,大家如果觉得我讲的不够清楚,也可以去看看这两篇文章。
Five-菜鸟级
Arabic1666
分析&解法
在解这道题之前,先要理解以下几点:
- 满足“任何两个认识的人不能分在同一个考场”这一要求的解不是唯一的,所以才需要求考场数最少的解,举个例子:4个考生,认识关系为:1 4;2 3;3 4;那么满足条件的解有{1,3}、{2,4},以及{1,2}、{3}、{4},那么肯定第一种是考场数最少的解法。
- 回溯法:递归+剪枝,回溯法采用试错的思想,它尝试分步的去解决一个问题。在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。
- 剪枝:借用博主[Arabic1666]的话来说就是,剪枝策略就是在搜索过程中利用过滤条件来剪去完全不用考虑(已经判断这条路走下去得不到最优解)的搜索路径,从而避免了一些不必要的搜索,大大优化了算法求解速度,还保证了结果的正确性。
应用到回溯算法中,我们就可以提前判断当前路径是否能产生结果集,如果否,就可以提前回溯。而这也叫做可行性剪枝
代码
可能注释有点杂乱,请见谅
#include<stdio.h>
#include<string.h>
#define N 110
#define min(a,b) a>b?b:a
int relaChart[N][N];//relational chart
int roomChart[N][N];//考场表
int n,rooms=N;//考生人数 考场数
void DFS(int x,int r);//分考场
int main(){
int m,i,a,b;
memset(relaChart,0,sizeof(relaChart));
memset(roomChart,0,sizeof(roomChart));
//用memset函数初始化两个表
scanf("%d\n%d",&n,&m);
while(m--){
scanf("%d %d",&a,&b);
relaChart[a][b]=relaChart[b][a]=1;
//建立a和b之间的认识关系
}
DFS(1,1); //从第一个人开始安排教室
printf("%d",rooms);
return 0;
}
void DFS(int x,int r){
int i,j;
if(r>=rooms) return;//剪枝!!
//rooms存的是已经找到的可行方案中最小的考场数
//如果现在的考场数r已经大于了rooms了,那么这个方案肯定是不行的了
if(x>n){
rooms=min(r,rooms);
return;
}
for(i=1;i<=r;i++){
j=0; //从每个考场第一个座位开始找
while(roomChart[i][j] && relaChart[roomChart[i][j]][x]==0)
//如果当前考场第j个座位有人并且两个人之间不认识
j++; //就继续找下一个座位
if(roomChart[i][j]==0){
//如果找到一个空位,那个该考生肯定跟前面的人都不认识
roomChart[i][j]=x;
//那么该考生的考场和座位就找到了
DFS(x+1,r);
roomChart[i][j]=0;
//回溯!!将x的位置清零,重新给它安排考场
}
}
roomChart[i][0]=x;
//如果找完所有考场也没有合适的座位,那么就只有增加一个考场了
DFS(x+1,r+1);
roomChart[i][0]=0;
回溯!!将x的位置清零,并返回到上一层函数,重新给它安排考场
}
示例图解
如果有看了代码还迷迷糊糊的同学,请看这里!!!
我以上面提到的4个考生,认识关系为:1 4;2 3;3 4;为例,逐步为你们讲解。
如果有问题的话,欢迎在评论区提问!