题目:民生问题

题目描述

某市政府非常关注民生,最近对民生问题作了调研,提出了最近要解决的n个民生问题,政府的专家顾问组有w人,每一个专家都有自己的特长,政府知道每专家能解决哪些问题,现在政府想知道至少请多少位专家,才能把所有的问题都解决?

输入输出格式

输入格式:

从文件question.in中读入数据,第一行两个整数n、w表示有n要解决的问题和w位专家,要解决的问题以1…n编号。接下来w行,第i+1行第一个数li表示第i位专家能解决问题的数量,接下来li个数表示第i位专家能解决的问题的编号。

输出格式:

将结果输出到文件question.out中,只有一个数,表示至少要请多少位专家。

输入输出样例

输入样例#1:

4 4
2 1 2
1 4
3 2 3 4
2 1 3

输出样例#1:

2

提示信息

数据范围:
对于40%的数据,3<=n,w <=10
对于100%的数据,3 <=n,w <=60,1<=li <=6

思路

看到这题的第一眼感觉是搜索,不过数据范围…
不过这题的正解就是搜索,只不过是加了很多很多的剪枝

先说说搜索的剪枝,分为可行性剪枝最优性剪枝.
显然,可以用到常规的最优性剪枝:在搜索时如果当前选择的专家数 > 当前最优值,直接return.

其次,就是不常规的剪枝:

1.如果专家a能解决的问题,专家b都能解决,那么专家a可以不纳入搜索范围别问我为啥

2.如果一个问题,只有专家a可以解决,那么专家a必选,同时不纳入搜索范围

说完剪枝,就是怎么搜的问题:
枚举每一个(这里指搜索范围以内的专家)专家选还是不选,直到所有问题被解决或已经遍历完所有专家(我一开始就是这样的)

枚举每一个未解决的问题选择哪一个专家,顺便标记该专家能解决的其它问题(有点拗).从第一个问题枚举到最后一个问题,同时跳过已解决的问题

显然,第二种效率更高
别问我这些都是怎么想到的(老师讲的~ )
另外,这题确实比较麻烦,要一步步来,不要急

大概思路就是这样,大家可以借助代码和注释理解:

AC代码

#include 
#include 
using namespace std;
int n , m , sum , ans , sol[110];
//sol[i]:问题i是否被解决(注意int类型,往下看就知道了) 
bool map[110][110], out[110];
//map[i][j]:false:i专家不能解决j问题,true:能解决,out[i]:i专家是否在搜索范围以内(true表示已经被踢出(不在范围内))
int p[110][110] , pn[110];//p[i][j]:i问题能被j专家解决(链表) ,pn[i]为能解决i问题的专家数量 
int q[110][110] , qn[110];//q[i][j]:i专家能解决j问题(链表) ,qn[i]为i专家能解决的问题数量 
void dfs(int x , int nowsum){
	if(x == m + 1){//记录最优值 
		if(nowsum < ans)
			ans = nowsum;
		return;
	}
	if(nowsum >= ans)//最优性剪枝 
		return;
	
	for(int i = 1 ; i <= pn[x] ; i++){
		for(int j = 1 ; j <= qn[p[x][i]] ; j++)//枚举可以解决x这个问题的专家可以解决的所有问题 
			sol[q[p[x][i]][j]]++;//可以解决x这个问题的专家可以解决的问题的专家+1 
			//这里注意:sol[i]表示当前能解决i问题的专家数量,sol[i]==0则i问题未解决,仔细想想为什么这样 
		
		int nex = x + 1;//同样,避免多次递归,提高效率 
		while(sol[nex] != 0 && nex <= m)nex++;
		dfs(nex , nowsum + 1);
		
		for(int j = 1 ; j <= qn[p[x][i]] ; j++)//回溯 
			sol[q[p[x][i]][j]]--;
	}
}
int main(){
	//input
	cin >> m >> n;
	for(int i = 1 ; i <= n ; i++){
		cin >> qn[i];
		for(int j = 1 ; j <= qn[i] ; j++){
			cin >> q[i][j];
			map[i][q[i][j]] = true;
		}
			
	}
	//判断包含关系 
	for(int i = 1 ; i <= n ; i ++)
		if(!out[i])
			for(int j = 1 ; j <= n ; j++)//判断i专家能解决的问题是否包含j专家 
				if(i != j && !out[j]){
					bool b = false;
					
					for(int k = 1 ; k <= m ; k++)
						if(!map[i][k] && map[j][k]){//如果有第i位专家可以解决但是第j位专家解决不了的题目就不能将第i位专家踢出 
							b = true;
							break;
						}
					if(!b){
						out[j] = true;
					}
				}
	//判断问题的唯一解  
	for(int i = 1 ; i <= m ; i ++){//枚举所有问题 
		if(sol[i] > 0)//如果这个问题已经有专家可以解决了就没必要继续往下判断
			continue;
		int k = -1;
		bool b = false;
		
		for(int j = 1 ; j <= n ; j ++)//枚举所有专家 
			if(map[j][i] && !out[j]){//如果这个问题已经有专家可以解决了并且当前第j位专家也可以解决(有两个及以上个专家可以解决此问题)就直接break 
				if(k != -1){
					b = true;
					break;
				}
				k = j;
			}
		if(b == false){
			out[k] = true;//把此专家踢出 
			sum ++;//把此专家可以解决的所有问题都加上 
			for(int j = 1 ; j <= m ; j++)//标记该专家能解决的问题 
				if(map[k][j])
					sol[j] += 1;
		}
			
	}
	//未解决的问题和 搜索范围内的专家建立链表(效率更高) 
	for(int i = 1 ; i <= n ; i++)
		if(!out[i])
			for(int j = 1 ; j <= m ; j ++)
				if(map[i][j]){
					pn[j]++;
					p[j][pn[j]] = i;
				}
	
	int beg = 1;//找到第一个没解决的问题,避免多次递归 
	while(sol[beg] != 0 && beg <= m)beg++;
	ans = 10000;
	dfs(beg , sum);
	cout << ans;
	return 0;
}

代码虽然有一点长,但还是很好理解的。如果还没有完全明白可以看看这篇博客(我就是看这个学会的!!!)

地址:https://blog.csdn.net/weixin_46304837/article/details/107851321

你可能感兴趣的:(算法,深度优先)