问题描述
题目分析
我们先简明介绍一下题目,我们仍然需要将所有点分成U、V两个集合。什么叫做顶点覆盖呢?即为V 集合中的每个点,至少与一个U集合中的点直接相连。如图所示(红色点表示U集合中的点):
我们可以看到V集合中的顶点2、5、6,都与至少一个U集合中的顶点直接相连。反而如果按照下图分配则不满足条件:
图中V集合的顶点2、5并没有U集合中的点与其直接相连,所以不是一种顶点覆盖。
那我们应该如何判断图是否被覆盖了呢?可以开辟一个数组c,如果c[j]==0,则表示U集合中没有任何一个顶点与其直接相连。
优先级
说完如何判断是否覆盖之后,我们来确定一下优先级。由于所有顶点都是带权的,我们的目的也是找到最小权覆盖,所以我们可以直接用权重作为优先级建立一个最小堆,从而实现优先队列。
界限函数
我们找的是最小点权,无法使用界限函数来对右孩子进行约束,因为如果当前结点不加入U集合中(即走右孩子路径),一定点权和更小,但是不一定会覆盖,所以不经过判断,我们也要将右孩子加入到队列中。
将活结点加入队列中
将活结点加入队列时,要对点的优先级、结果向量以及cover数组进行更新。
代码
#include <iostream>
#include <queue>
using namespace std;
class HeapNode
{
friend class VC;//求解最小权覆盖问题的类,融合了所有函数和所需的参数
public:
operator ()(int x,int y) const{return x < y;}//定义优先级
private:
int i,cn,*x,*c;//i表示结点序号,cn表示当前权重,x表示结果数组,c数组表示此时是否有一点i属于U,且i与j相连,如果有,则c[j]!=0
};
//解最小权顶点覆盖大类
class VC
{
friend MinCover(int **,int [],int);
private:
void BBVC();
bool cover(HeapNode E);//判断图是否已经被全部覆盖了
void AddLiveNode(priority_queue<HeapNode> &H,HeapNode E,int cn,int i,bool ch);
int **a,n,*w,*bestx,bestn;//邻接矩阵,节点数目,每个点的权重,结果向量,最优解
};
void VC::BBVC()
{
priority_queue<HeapNode> H(100000);
HeapNode E//扩展结点
E.x = new int [n+1];//开辟结果向量
E.c = new int [n+1];//开辟一数组,用于判断图是否被完全覆盖
for(int j = 1;j <= n;j++)
{
E.x[j] = E.c[j] = 0;
}
int i = 1,cn = 0;//初始化当前点权总和为0
while(true)
{
if(i > n)
{
if(cover(E))
{
for(int j = 1;j <= n;j++)
bestx[j]=E.x[j];
bestn = cn;
break;
}
}
else
{
if(!cover(E))//如果当前没有完全覆盖,就将这个点加入到U集合中
AddLiveNode(H,E,cn,i,1);
AddLiveNode(H,E,cv,i,0);
}
if(H.size()==0)
break;
E = H.top();
H.pop();
cn = E.cn;
i = E.i + 1;
}
}
//判断图是否完全覆盖
bool VC::cover(HeapNode E)
{
for(int j = 1;j <= n;j++)
{
if(E.x[j]==0 && E.c[j]==0)//如果此时j结点既不是U中的点,而且也没有U中的点与其相连,则至少这个点未被覆盖
{
return false;
}
}
return true;
}
void VC::AddLiveNode(priority_queue<HeapNode> &H,HeapNode E,int cn,int i,bool ch)
{
HeapNode N;//创建一个新的堆结点
N.x = new int [n+1];
N.c = new int [n+1];
for(int j = 1;j <= n;j++)
{
N.x[j] = E.x[j];
N.c[j] = E.c[j];
}
N.x[i] = ch;
if(ch)
{
N.cn = cn + w[i];//此时i要加入集合U,所以其权重应该加上cn
for(int j = 1;j <= n;j++)
{
if(a[i][j])
N.c[j]++;//表明此时对于结点j来说,有一节点i属于U与其连接,表明这个点被覆盖了
}
}
else
N.cn = cn;
N.i = i;
H.push(N);
}
//MinCover完成最小覆盖计算
int MinCover(int **a,int v[],int n)//v表示的是结点权重数组
{
VC Y;
Y.w = new int [n+1];
for(int j = 1;j <= n;j++)
Y.w[j] = v[j];
Y.a = a;
Y.n = n;
Y.bestx = v;
Y.BBVC();
return Y.bestn;
}
//主函数
int main()
{
int n,e,u,v;//结点数,边数,u,v为结点编号
cin>>n>>e;
int a[n+1][n+1];
for(int i = 0;i <= n;i++)
{
for(int j = 0;j <= n;j++)
{
a[i][j] = 0;//初始化为0
}
}
p = new int [n+1];//定义结果向量
for(int i = 1;i <= e;i++)
{
cin>>u>>v;
a[u][v] = 1;
a[v][u] = 1;
}
cout<<MinCover(a,p,n)<<endl;
for(int i = 1;i<=n;i++)
cout<<p[i]<<" ";
cout<<endl;
return 0;
}
总结
我们可以看到,判断是否覆盖需要O(n)的时间复杂度,活结点入队需要O(n)的时间复杂度。所以在BBVC函数中,时间复杂度应该为O(n3)(**个人理解,未必正确:while循环的时间复杂度与节点数目有关,所以是n级别的,在if(!cover(E))中,判断的时间复杂度为O(n),内部的活结点入队操作时间复杂度为O(n),所以BBVC的时间复杂度为O(n3)**)。在主函数中,MinCover与BBVC的时间复杂度相同,我们还需要输入邻接矩阵的边,所以时间复杂度为O(n^3+e)。(个人理解,如果有不同想法欢迎指出,谢谢)