【蓝桥】 历届试题 分考场(DFS,回溯,剪枝,无向图染色问题)

历届试题 分考场
时间限制:1.0s 内存限制:256.0MB

问题描述
  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

参考链接(特别鸣谢)
1.(蓝桥杯)分考场(无向图染色)https://blog.csdn.net/zxwsbg/article/details/80376847
2.蓝桥杯_ 历届试题 分考场 (图的着色问题)
https://blog.csdn.net/li1615882553/article/details/79694958
3.蓝桥杯 历届试题 分考场 (DFS)
https://blog.csdn.net/qq_41923622/article/details/80405277
4.蓝桥杯历届试题分考场-回溯搜索最值
https://blog.csdn.net/memeda1141/article/details/80301201

思路:
总体思路是:依次深搜每个学生,找到他们能去的房间,找不到能去的房间就开一个新房间,直到所有学生安排完成,得出结果ans。

通过回溯遍历出所有可能的安排情况,找出其中最小的ans。

剪枝那一步很关键,但不是很好弄懂,建议开始理解的时候先跳过。

这次尝试写出了第一个样例的过程伪代码,建议配合源代码一起分析。
耐心点看,应该会有所领悟的。

样例1过程伪代码:

int main()
{
	dfs(1,0) //判断第1个同学  目前房间数为0
	return 0;
}

dfs1(x=1,sum=0)//判断第1个同学  目前房间数为0
{
	if(sum>=ans) 	//此时ans为无穷大 ,判断为false,本伪代码没有涉及到剪枝,但剪枝非常重要 
	if(x==n+1)		//此时n=5, 判断为false
	for(i=1;i<=sum;)//此时sum=0,判断为false 
	
	f[sum+1][++c[sum+1]] = x;  //开设第一房间 一号房间的第一个人为x(x=1) 
	//dfs(x+1,sum+1);	 dfs(1+1,0+1)
	/*********************************第2层DFS内部****************************************/	
	dfs2(x=2,sum=1)//判断第2个同学  目前房间数为1 	//dfs(1+1,0+1)
	{
		for(i=1;i<=sum;i++)
		{
			int len=c[1]=1;
			for(int j=1;j<=len;j++)
			{
				if(!a[x][f[i][j]])//房间i的第j个人与x是否有关系
				//这里是a[2][f[1][1]]=a[2][1]=1,故房间i的第j个人与x有关系
				//此时k就不会++; 
			}
			if(k==len)//k<len,房间i里有人和当前学生x有关联,false
		}
		f[sum+1][++c[sum+1]] = x;  //开设房间2,房间2人数c[1+1],由0变成1 
	    //dfs(x+1,sum+1);	 dfs(2+1,1+1)
	/*********************************第3层DFS内部****************************************/		    
 		dfs3(x=3,sum=2)//判断第3名同学,当前房间数为2
		{
			for(i=1;i<=sum;i++)//对房间进行遍历
			{
				int len=c[1]=1;//当前房间人数为1 
				for(int j=1;j<=len;j++)
				{
					if(!a[x][f[i][j]])//判断当前x(x=3)号同学和房间i的同学是否有关联
					//这里 a[3][f[1][1]]=a[3][1]=1,所以有关联 
				} 
				if(k==len)//false 	
			//当i=2时,对房间2进行判断 
			{
				int len=c[2]=1;//房间2的人数为1
				for(int j=1;j<=len;j++)
				{
					if(!a[x][f[i][j]])//判断x=3号同学和房间i中的第j名同学的关系
					//当前 a[3][f[2][1]]=a[3][2]=1
				} 
				if(k==len)//k<len ,false 			 
			} 
			}
			f[sum+1][++c[sum+1]] = x; //开设房间3,房间3的人数由0变1 
			//dfs(x+1,sum+1); dfs(3+1,2+1)
	/*********************************第4层DFS内部****************************************/				
			dfs4(x=4,sum=3)//对第4名同学进行判断,此时房间有三个
			{
				for(i=1;i<=sum;i++)
				//对已有的三个房间遍历,发现4号同学与三个房间的三名同学都认识
				f[sum+1][++c[sum+1]] = x;  //开设房间4 
				//dfs(x+1,sum+1);dfs(4+1,3+1)
	/*********************************第5层DFS内部****************************************/			  				 				
				dfs5(x=5,sum=4)//对第五名同学进行判断,此时房间有4个
				{
					for(int i=1;i<=sum;i++)//对前四间房间进行遍历
					{
						int len=c[1]=1;//一号房间一个人
						for(int j=1;j<len;j++)
						{
							if(!a[x][f[i][j]])//判断x=5号同学和第i个房间第j个人的关系
							//这里时第一个房间第一个人,a[x][f[i][j]=a[5][f[1][1]]=a[5][1]=0
							//故他俩不认识,k++ 
						 } 
						 if(k==len)//k==len==1
						 {
						 	f[i][++c[i]]=x;//第i个(第一个)房间 的人数++
							//f[1][2]=5,一号房间第二个人是五号学生
							//dfs(x+1,sum); dfs(5+1,4)此时的房间数就不用增加了 
	/*********************************第6层DFS内部****************************************/			  				 						
							dfs6(x=6,sum=4)
							{
								if(x==n+1)//此时x==n+1==6
								{
									ans=min(ans,sum)//ans的初始值很大,此时sum=4
									//故ans=4;
									return; 
								}	
							}
	/*********************************第6层DFS内部****************************************/			  				 							
							c[i]--; //回溯 
						 }
					} 
				} 
	/*********************************第5层DFS内部****************************************/			  				 
				c[sum+1]--; //回溯
			}
	/*********************************第4层DFS内部****************************************/			  
			c[sum+1]--; //回溯
		}  
	/*********************************第3层DFS内部****************************************/	
	    c[sum+1]--; //回溯
	} 
	/*********************************第2层DFS内部****************************************/	
	c[sum+1]--; //回溯
} 

AC代码

#include <bits/stdc++.h>
using namespace std;
const int N=105;
int n,m;	//n名学生  m种关系
int a[N][N];//用于表示任意两名同学间的联系
int f[N][N];//用于表示第i个房间的第j个人是谁 
int c[N];	//用于记录当前房间有多少人 
int ans=N;	//ans为所需最少房间数 ,开始时将其标记成较大数(人数最多100人)
void dfs(int x,int sum)
{
	if(sum>=ans)//剪枝,很关键,能节省大量时间,ans开始标记成较大数对此语句无影响 
	{
		return;
	}
	if(x==n+1)//所有同学安排好房间了
	{
		ans=min(ans,sum);//判断已有的ans和当前sum谁更小 
		return;
	} 
	for(int i=1;i<=sum;i++)
	{
		int len=c[i],k=0;//len和c[i]为当前房间人数 
		for(int j=1;j<=c[i];j++)
		{
			if(!a[x][f[i][j]])//不认识,k就++ 
			k++;
		}
		if(k==len)//当前房间所有人都不认识x时为true,只要有一个人认识x就会有k<len 
		{
			f[i][++c[i]]=x;//房间i的人数c[i]++,标号为x号学生 
			dfs(x+1,sum);//继续深搜,判断下一个学生 ,房间数不用增加 
			--c[i]; //回溯回来,要把当前所在房间数减一		
		}
	}
	sum++;//当前同学没有房间能去,新开一个房间 
	f[sum][++c[sum]]=x;//房间sum的第c[sum]为x,c[sum]由0变1
	dfs(x+1,sum);//继续深搜,判断下一个学生,sum是已经增加1的房间数 
	--c[sum]; //回溯回来,要把当前所在房间数减一
}
int main()
{
	cin>>n>>m;	//n名学生  m种认识状态 
	for(int i=0;i<m;i++)
	{
		int x,y;
		cin>>x>>y;			//读入关系 
		a[x][y]=a[y][x]=true;	//x,y互相认识建立联系 
	}
	dfs(1,0);//从第一个人,房间数为0开始搜索 
	cout<<ans<<endl;
	return 0;
}

在挂一个学长的代码(这题最早给我启发的代码)

#include<bits/stdc++.h>
using namespace std;
bool mp[110][110];
int tuan[110][110];
int cnt=0, ans=INT_MAX;
int n, m;

bool check(int p, int b){
	int sz=tuan[b][0];
	
	for(int i=1; i<=sz; i++){
		if(!mp[tuan[b][i]][p]) return false;
	} 
	return true;
}

void dfs(int p){
	if(p==n+1){
		ans=min(ans, cnt);
		return;
	}	
	
	if(cnt>=ans) return;
	
	for(int i=1; i<=cnt; i++){
		if(check(p, i)){
			tuan[i][++tuan[i][0]]=p;
			dfs(p+1);
			tuan[i][0]--;
		}
	}
	++cnt;
	tuan[cnt][++tuan[cnt][0]]=p;
	dfs(p+1);
	tuan[cnt][0]=0;
	cnt--;
	return;
}

int main(){
	
	scanf("%d%d", &n, &m);
	
	memset(mp, true, sizeof mp);
	memset(tuan, 0, sizeof tuan);
	
	for(int i=1; i<=m; i++){
		int u, v;
		scanf("%d%d", &u, &v);
		mp[u][v]=mp[v][u]=false;	
	}
	
	
	dfs(1);
	
	printf("%d\n", ans);
	
	return 0;
}

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值