HDU6150&&2017CCPC网络赛Vertex Cover

这一篇博客可能是最详细的了,如果帮助了你,请顶一下,谢谢支持。

2017年CCPC网络选拔赛题目。现HDU6150

题目的意思是求最小顶点覆盖是一个NP问题(NP问题可自行百度),没有一个好的算法去求。但是特殊图的时候有方法去求。
现在说找到一种求最小顶点覆盖的算法。有点贪心的思想在里面。
每次找图中度数最大的点(并且是编号最后一个点),把这个点加入解的集合,然后把和这个点所有关联的边也删除(即是与之相关联的点度数减1)
如此循环下去,一定会到所有点取完,或者还存在一些点,但度数都为0这两种情况。
此时跳出循环。这时候就是这个算法的解。
这一步骤在题目给的伪代码中都可以看出来
我给大家稍微解释下伪代码
for (int i = 1; i <= n; ++i) {
  use[i] = false; //清空标记数组use
    deg[i] = degree of the vertex i;//统计每个顶点的度数
}
int ans = 0;
while (true) {//死循环,因为不知道最后会选择多少个点
  int mx = -1, u;
    for (int i = 1; i <= n; ++i) {
      if (use[i])//这个点已经被删除
          continue;
        if (deg[i] >= mx) {//找出度数最大并且编号最大的顶点
          mx = deg[i];
            u = i;
        }
    }
    if (mx <= 0)//找不到点
      break;
    ++ans;
    use[u] = true;
    for (each vertex v adjacent to u)//把与之关联的点的度数减1
      --deg[v];
}
return ans;


如果你还不是很理解,那我举个例子
 
比如说这么一个图,现在每个点的度数都是2,。
按照上面的代码,第一个找到的点就是4,那把与4相关边都删除,删除14,34;
第二个找到的点就是2,那么此时删除12,23
最后只剩下1和3,但是这两个点没有度数。所以跳出了循环。


题目给的贪心算法是这样,但这个算法有BUG。
现在题目要求我们输出一个无向图,用题目给出的算法求得最小顶点覆盖个数是正确答案的3倍或以上。
输出这个图的顶点总个数和总边数,然后输出每条边。
最后输出最小顶点覆盖的答案,以及选择的顶点编号。


那么我们就像构造一个二分图,一边顶点个数是N个,另一遍顶点个数是3N,正确答案是N,用贪心算法求得的答案是3N。
接下来就是构造的巧妙的地方。
首先我们确定N个顶点,然后进行N次分组。每次分组i,在二分图右边新建N/I个新的顶点,并与每一组中个i个顶点相连,那么这样就多了N/I*I条边。(这里有隐含的向下取整)
这样进行了N次分组,右边的点大概有n*log(2,n)个点。
(如果没理解,下面有例子)
这样当N足够大的时候,右边的点就是左边点的3倍及以上。
这时候,我就得到了答案


你可能会疑问为什么这样的构造就能保证用题目的贪心算法得到就是右边的顶点?
那我们稍微证明下,首先看到题目的伪代码,它每次找到的是度数,编号都是最大的顶点。
这个一个比较关键的地方。
按照这样方法构造出来的图,左边顶点最大度数为N,从1-N可能都有,但不一定都是连续的。
右边顶点最大度数也为N,但是从1-N都有。
按照题目算法,每次找度数最大的点,首先找到的肯定是度数为N的点。
然后我们需要编号比较大的,那么这个顶点在右边。
删除与这个顶点相关联的边以后,情况变成了找度数为N-1的点,情况和上面一样。
最后我们找度数为1的点,也在右边。(可以选择N=5的时候手动模拟一下)


例子:N=5;跟着我的步骤写下去
先找5个点,编号1-5;
然后第一次分组,i=1;n/i=5;所以增加了16,27, 38,49,510这五条边
接着第二次分组,i=2,n/i=2;所以增加了111,211,312,412这四条边。
一次类推。
删除的时候反向操作。数据比较小,模拟一下就懂了。
把下面程序中N的改一改就能得到所有的边

去掉代码注释可以看得更明显。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<functional>
using namespace std;

int main()
{
	int n=15,m=n,cnt=2*n;//n=15的时候就符合条件了,大于15就更可以了
	int i,j,k,c=2*n;
	int sum=0;
	for(i=2;i<=n;i++)//更新每次分组以后的顶点个数和边数
	{
		m+=n/i*i;
		cnt+=n/i;
	}
	printf("%d %d\n",cnt,m);
	for(i=1;i<=n;i++)
		printf("%d %d\n",i,i+n);
	for(i=2;i<=n;i++)//每一次分组中顶点的个数
	{
		for(j=0;j<(n/i);j++)//控制组数
		{
			sum=1+(i*j);//编号
			//printf("每组顶点个数=%d 组数=%d 左边开始位置=%d\n",i,j,sum);
			for(k=sum;k<sum+i;k++)
			{
				if(k%i==1||(n/i==1&&k==1))//右边顶点配对i个以后到下一个顶点
					c++;//后面是特殊情况,只有一组的时候特判下
				printf("%d %d\n",k,c);
			}
		}
	}
	printf("%d\n",n);//最小顶点覆盖答案
	for(i=1;i<=n;i++)//就是左边的N个顶点
		printf("%d\n",i);
	return 0;
}


全部评论

相关推荐

球球别再泡了:坏,我单9要了14
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务