HDU1530 最大团 模板

Maximum Clique

Time Limit: 20000/10000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 4114    Accepted Submission(s): 2175

Problem Description
Given a graph G(V, E), a clique is a sub-graph g(v, e), so that for all vertex pairs v1, v2 in v, there exists an edge (v1, v2) in e. Maximum clique is the clique that has maximum number of vertex.
 
Input
Input contains multiple tests. For each test:

The first line has one integer n, the number of vertex. (1 < n <= 50)

The following n lines has n 0 or 1 each, indicating whether an edge exists between i (line number) and j (column number).

A test with n = 0 signals the end of input. This test should not be processed.

 
Output
One number for each test, the number of vertex in maximum clique.
 
Sample Input
5
0 1 1 0 1
1 0 1 1 1
1 1 0 1 1
0 1 1 0 1
1 1 1 1 0
 
Sample Output
4
 
Author
CHENG, Long
 
Source
 
题意:
求最大团;最大团:
参考:http://www.cnblogs.com/yefeng1627/archive/2013/03/31/2991592.html  
        http://www.cnblogs.com/zhj5chengfeng/p/3224092.html

一、定义

一个无向图 G=(V,E),V 是点集,E 是边集。取 V 的一个子集 U,若对于 U 中任意两个点 u 和 v,有边 (u,v)∈E,那么称 U 是 G 的一个完全子图。 U 是一个团当且仅当 U 不被包含在一个更大的完全子图中。

G的最大团指的是定点数最多的一个团。

二、常用做法

1、顺序贪婪启发式搜索算法

2、局部搜索启发式算法

3、智能搜索启发式算法

4、遗传算法

5、模拟退火算法

6、禁忌算法

7、神经网络算法

8、改进蚁群算法-AntMCP

看了所列出的算法,是不是有一种头皮发麻的感觉。反正我是这样的感觉...因为上面的东西我都不会...

如果你想看上面的东西,百度百科中有一些简略的介绍,我太弱,没看懂。

百度百科传送门:最大团问题

下面说说常用的一种搜索算法

当然,这种算法很不高效,所以当图中有 100 个点以上时,请慎用

先看看一个显而易见的 DFS :

    初始化:

      从一个点 u 开始,把这个点加入到一个集合中,设为 U。遍历一遍所有和他相连的点,把他们放入另一个集合 S1 中,接下来进行第一遍 DFS

    第一遍 DFS :

      从 S1 中选择一个点 u1,这个点肯定和集合 U 中的任何一个点相连。把集合 S1 中 u1 能访问到的点加入到集合 S2 中,并把 u1 加入到集合 U 中,进行第二遍 DFS

    第二遍 DFS :

      从 S2 中选择一个点 u2,这个点肯定和集合 U 中的任何一个点相连。把集合 S2 中 u2 能访问到的点加入到集合 S3 中,并把 u2 加入到集合 U 中,进行第三遍 DFS

    第三遍 DFS :

      从 S3 中选择一个点 u3,这个点肯定和集合 U 中的任何一个点相连。把集合 S3 中 u3 能访问到的点加入到集合 S4 中,并把 u3 加入到集合 U 中,进行第四遍 DFS

    ......

    最底层的 DFS :

      当某个 S 集合为空集的时候,DFS 结束,这时候我们就找到了一个完全子图,用这个完全子图更新我们的最大团。退出当前的 DFS,返回上层 DFS,接着找下一个完全子图,直到找完所有的完全子图

按照上面介绍的 DFS 方法,肯定能够得到一个最大团,因为该 DFS 把所有的完全子图都枚举了一遍。但是这样做的时间复杂度是不是太高了?

于是产生了下面的 DFS 过程,大致上和上面的 DFS 一样,只不过有一些地方不太一样了

首先,我们先得到后几个点组成的最大团到底是多大,(最开始的时候肯定是最后一个点单独构成一个最大团,点数为1)然后我们再 DFS:

    初始化:

      从一个点 u 开始,把这个点加入集合 U 中。将编号比它大的且和它相连的点加入集合 S1 中,为了方便,将集合 S1 中的点有序,让他们从小到大排列,进行第一遍 DFS

    第一遍 DFS :

      从 S1 中选择一个点 u1,遍历 S1 中,所有编号比 u1 大且和 u1 相连的点,其实也就是排在 u1 后面,并且和 u1 相连的点,将它们加入集合 S2 中。同理,让 S2 中的点也按照编号也从小到大排列。将 u1 加入集合 U 中,进行第二遍 DFS

    第二遍 DFS :

      从 S2 中选择一个点 u2,遍历 S2 中,所有排在 u2 后面且和 u2 相连的点,并把它们加入集合 S3 中,让 S3 中的点按照编号从小到大排列,将 u2 加入集合 U 中进行第三遍 DFS

    第三遍 DFS :

      从 S3 中选择一个点 u3,遍历 S3 中,所有排在 u3 后面且和 u3 相连的点,并把它们加入集合 S4 中,让 S4 中的点按照编号从小到大排列,将 u3 加入集合 U 中进行第四遍 DFS

    ......

    最底层的 DFS :

      当某个 S 集合为空时,DFS 过程结束,得到一个只用后面几个点构成的完全子图,并用它去更新只用后面几个点构成的最大团。退出当前 DFS,返回上层 DFS,接着找下一个完全子图,直到找完所有的完全子图

上面的 DFS 过程,如果不加任何剪枝的话,其实和第一个 DFS 是差不多的,但是既然我们都这样 DFS 了,能不能想一想怎么剪枝呢?

假设我们当前处于第 i 层 DFS,现在需要从 Si 中选择一个 ui,把在 Si 集合中排在 ui 后面的和 ui 相连的点加入集合 S(i+1) 中,把 ui 加到集合 U 中

可能大家稍作思考之后就想到了一个剪枝:

剪枝1:如果 U 集合中的点的数量+1(选择 ui 加入 U 集合中)+Si 中所有 ui 后面的点的数量 ≤ 当前最优值,不用再 DFS 了

还有什么剪枝呢?

注意到我们是从后往前选择 u 的,也就是说,我们在 DFS 初始化的时候,假设选择的是编号为 x 的点,那么我们肯定已经知道了用 [x+1, n] ,[x+2, n],[x+3, n] ...[n,n] 这些区间中的点能构成的最大团的数量是多大

剪枝2:如果 U 集合中的点的数量+1(理由同上)+[ui, n]这个区间中能构成的最大团的顶点数量 ≤ 当前最优值,不用再 DFS了

有这两个剪枝就够了吗?

不,我们还能想出一个剪枝来:

剪枝3:如果 DFS 到最底层,我们能够更新答案,不用再 DFS 了,结束整个 DFS 过程,也不再返回上一层继续 DFS 了

为什么?因为我们如果再继续往后 DFS 的话,点的编号变大了,可用的点变少了(可用的点在一开始 DFS 初始化的时候就确定了,随着不断的加深 DFS 的层数,可用的点在不断的减少)

有了上面三个剪枝,100 个点以内的图,我们也能非常快的出解了

可能有人会问,如果想知道最大团包含哪些节点该怎么办?

这还不简单?每次 DFS 都会加一个点进入 U 集合中,DFS 到最底层,更新最大团数量的时候,U 集合中的点一定是一个完全子图中的点集,用 U 集合更新最大团的点集就行了

三、常用结论

1、最大团点的数量=补图中最大独立集点的数量

2、二分图中,最大独立集点的数量+最小覆盖点的数量=整个图点的数量

3、二分图中,最小覆盖点的数量=最大匹配的数量

4、图的染色问题中,最少需要的颜色的数量=最大团点的数量

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,best;
int mp[][],num[];
bool dfs(int s[],int sum,int cnt)// sum: 与u相连的顶点数量 , cnt表示当前团的数量
{
if(sum==){// 当此团中最后一个点 没有 比起序号大 的顶点相连时
if(cnt>best){// 问题1:best为最大团中顶点的数量
best=cnt;
return ;
}
return ;
}
int t[];
for(int i=;i<sum;i++){// 枚举每一个与 u 相连的顶点 s[i]
if(cnt+(sum-i)<=best) return ;// 剪枝1, 若当前 顶点数量cnt 加上还能够增加的最大数量 仍小于 best则 退出并返回false
if(cnt+num[s[i]]<=best) return ;// 剪枝2, 若当前 顶点数量cnt 加上 包含s[i]的最大团顶点数 仍小于 best则 退出并返回false
int k=;
for(int j=i+;j<sum;j++){// 扫描 与u相连的顶点 中与 s[u]相连的顶点 并存储到 数组 t[]中,数量为k
if(mp[s[i]][s[j]]) t[k++]=s[j];
}
if(dfs(t,k,cnt+)) return ;
}
return ;
}
int Maxt()
{
if(n<=) return ;
int s[];
best=;
for(int i=n-;i>=;i--){
int k=;
for(int j=i+;j<n;j++){// 遍历 [i+1, n] 间顶点,
if(mp[i][j]) s[k++]=j;
}
dfs(s,k,);
num[i]=best;// 得出顶点 i, 出发构成最大团 中顶点数量
}
return best;
}
int main()
{
while(scanf("%d",&n)&&n){
for(int i=;i<n;i++)
for(int j=;j<n;j++)
scanf("%d",&mp[i][j]);
printf("%d\n",Maxt());
}
return ;
}
上一篇:Yum源的优先级


下一篇:Hibernate的一对多实例