民生问题C++

题目

项目问题 (就是民生问题,一模一样)
注:题目来自东莞2019年特长生测试第2题

题目描述:

张三是某项目工程总经理,在工程开发时遇到了 n 个问题,张三的团队中共
有 w 人,每一个人都有自己的特长,由于还有其他工作要做,希望解决这 n 个问
题最尽安排最少的人员,现在张三想知道至少安排多少个人,才能把所有的问题 都解决?
数据输入:
从文件 question.in 中读入数据,第一行两个整数 n、w 表示有 n 要解决的
问题和张三的员工有 w 人,要解决的问题以 1..n 编号。接下来 w 行,第 i+1 行
第一个数 li 表示第 i 个人能解决问题的数量,接下来 li 个数表示第 i 个人能解
决的问题的编号。
数据输出:
将结果输出到文件 question.out 中,只有一个数,表示至少要安排的人数。
3

输入输出样例:

question.in
4 4
2 1 2
1 4
3 2 3 4
2 1 3
question.out
2

数据范围:

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

附上民生问题原题

(题目来源:https://oj.dgzx.net/front/problem/ToProblemDetailBlank/3637)

【问题描述】
某市政府非常关注民生,最近对民生问题作了调研,提出了最近要解决的n个民生问题,政府的专家顾问组有w人,每一个专家都有自己的特长,政府知道每专家能解决哪些问题,现在政府想知道至少请多少位专家,才能把所有的问题都解决?
输入输出格式
输入格式:
从文件question.in中读入数据,第一行两个整数n、w表示有n要解决的问题和w位专家,要解决的问题以1..n编号。接下来w行,第i+1行第一个数li表示第i位专家能解决问题的数量,接下来li个数表示第i位专家能解决的问题的编号。
输出格式:
将结果输出到文件question.out中,只有一个数,表示至少要请多少位专家。
样例输入输出同上

附上测评系统

https://www.luogu.com.cn/problem/U127339

思路

思路

第一眼看到这题,搜啊!!!
看到数据范围,沉默......(貌似 不能过)
但是,正解就是搜索加了一堆剪枝

先说说搜索的剪枝,分为可行性剪枝和最优性剪枝.
显然,可以用到常规的最优性剪枝(并不难想到):如果当前选择的专家数 > 当前最优值,直接return.
其次,就是不常规的剪枝:

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

说完剪枝,就是怎么搜的问题:

  1. 枚举每一个(这里指搜索范围以内的专家)专家选还是不选,直到所有问题被解决或已经遍历完所有专家(我一开始就是这样的)
  2. 枚举每一个未解决的问题选择哪一个专家,顺便标记该专家能解决的其它问题(有点拗).从第一个问题枚举到最后一个问题,同时跳过已解决的问题

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

测评结果

完整版测评结果:
在这里插入图片描述
去掉最优性剪枝测评结果:
在这里插入图片描述

去掉剪枝1(专家之间的包含关系)测评结果:
在这里插入图片描述

去掉剪枝2(能解决问题的唯一专家)测评结果:
去掉剪枝2

代码

#include <iostream>
#include <cstdio>
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问题(链表) 
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++)
			sol[q[p[x][i]][j]]++;
			//这里注意: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(){
	freopen("question.in" ,"r" , stdin);
	freopen("question.out" ,"w" , stdout);
	//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]){
							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]){
				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;
}
posted @ 2020-11-11 07:04  追梦人1024  阅读(168)  评论(0编辑  收藏  举报