某市政府非常关注民生,最近对民生问题作了调研,提出了最近要解决的n个民生问题,政府的专家顾问组有w人,每一个专家都有自己的特长,政府知道每专家能解决哪些问题,现在政府想知道至少请多少位专家,才能把所有的问题都解决?
从文件question.in中读入数据,第一行两个整数n、w表示有n要解决的问题和w位专家,要解决的问题以1…n编号。接下来w行,第i+1行第一个数li表示第i位专家能解决问题的数量,接下来li个数表示第i位专家能解决的问题的编号。
将结果输出到文件question.out中,只有一个数,表示至少要请多少位专家。
4 4
2 1 2
1 4
3 2 3 4
2 1 3
2
数据范围:
对于40%的数据,3<=n,w <=10
对于100%的数据,3 <=n,w <=60,1<=li <=6
看到这题的第一眼感觉是搜索,不过数据范围…
不过这题的正解就是搜索,只不过是加了很多很多的剪枝
先说说搜索的剪枝,分为可行性剪枝和最优性剪枝.
显然,可以用到常规的最优性剪枝:在搜索时如果当前选择的专家数 > 当前最优值,直接return.
其次,就是不常规的剪枝:
1.如果专家a能解决的问题,专家b都能解决,那么专家a可以不纳入搜索范围(别问我为啥)
2.如果一个问题,只有专家a可以解决,那么专家a必选,同时不纳入搜索范围
说完剪枝,就是怎么搜的问题:
枚举每一个(这里指搜索范围以内的专家)专家选还是不选,直到所有问题被解决或已经遍历完所有专家(我一开始就是这样的)
枚举每一个未解决的问题选择哪一个专家,顺便标记该专家能解决的其它问题(有点拗).从第一个问题枚举到最后一个问题,同时跳过已解决的问题
显然,第二种效率更高
别问我这些都是怎么想到的(老师讲的~ )
另外,这题确实比较麻烦,要一步步来,不要急
大概思路就是这样,大家可以借助代码和注释理解:
#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