题目
原始部落中,居民们为了争夺有限的资源经常发生冲突。几乎每个居民都有自己的仇敌。部落酋长为了组织一支保卫部落的队伍,希望从部落的居民中选出最多的居民入伍,并保证队伍中任何2个人都不是仇敌。
问题分析
这个问题我们可以转化为图来理解,把居民看作顶点,如果两个居民不是仇敌,那么对应的两个顶点之间就有边,如果是仇敌,那么对应两个顶点之间就没有边,设所有居民及其之间的关系构成的图为G,要使得任意两个居民之间都不是仇敌关系,也就是护卫队中的居民构成的子图为完全图(任意两个顶点之间都有边)。而且要求居民数最多,就是求顶点数最多的极大完全子图(不存在更大的完全子图包含这个图)。
算法
回溯法
对于一些基础概念,这里不再赘述,如感兴趣可参考0-1背包问题系列2。
算法核心
这个问题跟之前写的0-1背包问题回溯法非常像,也是用约束条件判断能否生成左子树,用限界条件判断能否生成右子树,还有就是回溯,这里的回溯比之前的更简单,只需要对当前选择的居民人数运算即可,还是怎么加上的怎么剪掉。
约束条件,当前待放入顶点是否与之前已选择的顶点之间都有边相连,这里是判断加入这个顶点之后,选择的顶点能否构成完全图,如果当前待放入顶点与之前选择的某个顶点没有边相连,那么这个顶点肯定不能放入。如果满足约束条件,就可以生成左孩子结点,继续扩展得到左子树。
限界条件,当前待放入顶点之前的各个顶点的状态已经确定,也就是当前选择的人数已经确定,如果之后所有顶点都放入情况下的人数还是比之前计算得到的最优解小的话,那么这个分支可以直接剪掉了,因为就算之后的顶点都放进去也不可能比最优解多。所以限界条件就是,当前已选择人数+之后所有人数>之前计算得到的最大人数。如果满足限界条件就可以生成右孩子结点,继续扩展得到右子树。
代码实现
#include<iostream>
#include<cstring>
using namespace std;
const int maxn=105;
int m[maxn][maxn];//存储友好关系图
int x[maxn],cn;//当前选择,当前人数
int bestx[maxn],bestn;//最优选择,最大人数
int n,rn;//人数,友好关系数
bool join(int k)
{
bool flag=true;
for(int i=1; i<k; ++i)
if(x[i]&&m[i][k]==0)//与已选择的一个成员没有友好关系
{
flag=false;
break;
}
return flag;
}
void dfs(int t)
{
if(t>n)
{
for(int i=1; i<=n; ++i)
bestx[i]=x[i];
bestn=cn;
return ;
}
if(join(t))//如果满足约束条件,生成左子树
{
x[t]=1;
cn++;
dfs(t+1);
cn--;
}
if(cn+n-t+1>bestn)//如果满足限界条件,生成右子树
{
x[t]=0;
dfs(t+1);
}
}
void init()
{
cn=0;
bestn=0;
memset(x,0,sizeof(x));
memset(bestx,0,sizeof(bestx));
}
int main()
{
cout<<"请输入人数:";
cin>>n;
cout<<"请输入友好关系数:";
cin>>rn;
int i,a,b;
memset(m,0,sizeof(m));
cout<<"请输入关系友好的两人:";
for(i=0; i<rn; ++i)
{
cin>>a>>b;
if(!m[a][b])
m[a][b]=1;
}
init();
dfs(1);
cout<<"护卫队的最大人数为"<<bestn<<endl;
cout<<"护卫队人选为";
for(i=1; i<=n; ++i)
if(bestx[i])
cout<<i<<" ";
cout<<endl;
return 0;
}
参考文献:《趣学算法》