最大团解析及用处
题目:
求一张图中。最多的点集的最大个数。称为最大团。
////
下面是转载他人的博客:JMJST
从一个点 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、图的染色问题中,最少须要的颜色的数量=最大团点的数量
1、先来一道裸题:ZOJ 1492 Maximum Clique
/*
题目形式:最大团
给了一个最多包括 50 个点的无向图,
让求这个图中最大团所包括的的点的数量
*/
const int MAXN = 60;
class maxClique{
public:
static const int N = 60; //数据范围
bool DFS(int cur,int tot);
int maxclique();
bool G[N][N];
int n,Max[N],Alt[N][N],ans;
//Max:当前集合最大点数,Alt:集合
};
bool maxClique::DFS(int cur,int tot){
if(cur == 0){
if(tot > ans){
ans = tot;
return true;
}
return false;
}
for(int i = 0;i < cur;++i){
if(cur - i + tot <= ans) //剪枝1
return false;
int u = Alt[tot][i];
if(Max[u] + tot <= ans) // 剪枝2
return false;
int next = 0;
for(int j = i + 1;j < cur;++j)
if(G[u][Alt[tot][j]])
Alt[tot+1][next++] = Alt[tot][j];
if(DFS(next,tot+1))
return true;
}
return false;
}
int maxClique::maxclique(){
ans = 0;
memset(Max,0,sizeof(Max));
for(int i = n - 1;i >= 0;--i){
int cur = 0;
for(int j = i + 1;j < n;++j)
if(G[i][j]) Alt[1][cur++] = j;
DFS(cur,1);
Max[i] = ans;
}
return ans;
}
给了平面上 n 个点。要求选出 k 个点来,使得这 k 个点中,距离近期的两个点的距离最大。n 最大为50
二分答案后。假设两个点之间的距离大于当前的推断值,加边。在用最大团跑一下。依据得到最大团点的数量和 k 的大小关系。调整二分的上下界/。
一開始二分搜索次数太大了,结果超时了!!这个教训一定要记住啊!!。!
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
/*
题目形式:最大团
给了一个最多包括 50 个点的无向图,
让求这个图中最大团所包括的的点的数量
*/
const int MAXN = 60;
class maxClique{
public:
static const int N = 60; //数据范围
bool DFS(int cur,int tot);
int maxclique();
bool G[N][N];
int n,Max[N],Alt[N][N],ans;
//Max:当前集合最大点数。Alt:集合
};
bool maxClique::DFS(int cur,int tot){
if(cur == 0){
if(tot > ans){
ans = tot;
return true;
}
return false;
}
for(int i = 0;i < cur;++i){
if(cur - i + tot <= ans) //剪枝1
return false;
int u = Alt[tot][i];
if(Max[u] + tot <= ans) // 剪枝2
return false;
int next = 0;
for(int j = i + 1;j < cur;++j)
if(G[u][Alt[tot][j]])
Alt[tot+1][next++] = Alt[tot][j];
if(DFS(next,tot+1))
return true;
}
return false;
}
int maxClique::maxclique(){
ans = 0;
memset(Max,0,sizeof(Max));
for(int i = n - 1;i >= 0;--i){
int cur = 0;
for(int j = i + 1;j < n;++j)
if(G[i][j]) Alt[1][cur++] = j;
DFS(cur,1);
Max[i] = ans;
}
return ans;
}
struct Point{
double x,y;
double dist(const Point& a){
return(sqrt((x - a.x)*(x - a.x) + (y - a.y)*(y - a.y)));
}
}A[MAXN];
int N,K;
maxClique mc;
void build(double R){
mc.n = N;
for(int i = 0;i < mc.n;++i){
for(int j = 0;j < mc.n;++j){
if(A[i].dist(A[j]) >= R) mc.G[i][j] = 1;
else mc.G[i][j] = 0;
}
}
}
int main()
{
//freopen("Input.txt","r",stdin);
while(~scanf("%d%d",&N,&K)){
for(int i = 0;i < N;++i){
scanf("%lf%lf",&A[i].x,&A[i].y);
}
double lb = 0,ub = 20000;
for(int k = 0;k < 40;++k){
double mid = (lb + ub) / 2;
build(mid);
if(mc.maxclique() >= K)
lb = mid;
else
ub = mid;
}
printf("%.2lf\n",lb);
}
return 0;
}
3、来一个一般无向图最大独立集的题目:POJ 1419 Graph Coloring
给了一个有 n 个点 m 条边的无向图,要求用黑、白两种色给图中顶点涂色,相邻的两个顶点不能涂成黑色,求最多能有多少顶点涂成黑色。图中最多有 100 个点
利用上面提到的结论:最大团点的数量=补图中最大独立集点的数量。建立补图,求最大团就可以
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
/*
题目形式:最大团
给了一个最多包括 50 个点的无向图。
让求这个图中最大团所包括的的点的数量
*/
const int MAXN = 60;
class maxClique{
public:
static const int N = 106; //数据范围
bool DFS(int cur,int tot);
int maxclique();
bool G[N][N];
int Max[N],Alt[N][N];
int x[N],y[N];
int n,ans,*path,*res;
//Max:当前集合最大点数,Alt:集合
};
bool maxClique::DFS(int cur,int tot){
if(cur == 0){
if(tot > ans){
swap(path,res);
ans = tot;
return true;
}
return false;
}
for(int i = 0;i < cur;++i){
if(cur - i + tot <= ans) //剪枝1
return false;
int u = Alt[tot][i];
if(Max[u] + tot <= ans) // 剪枝2
return false;
int next = 0;
for(int j = i + 1;j < cur;++j)
if(G[u][Alt[tot][j]])
Alt[tot+1][next++] = Alt[tot][j];
path[tot+1] = u;
if(DFS(next,tot+1))
return true;
}
return false;
}
int maxClique::maxclique(){
ans = 0;
memset(Max,0,sizeof(Max));
path = x; res = y;
for(int i = n - 1;i >= 0;--i){
int cur = 0;
path[1] = i; //起点
for(int j = i + 1;j < n;++j)
if(G[i][j]) Alt[1][cur++] = j;
DFS(cur,1);
Max[i] = ans;
}
return ans;
}
int N,K;
maxClique mc;
int main()
{
// freopen("Input.txt","r",stdin);
int T,m;
scanf("%d",&T);
for(int kase = 1;kase <= T;++kase){
scanf("%d%d",&mc.n,&m);
memset(mc.G,true,sizeof(mc.G));
for(int i = 0,a,b;i < m;++i){
scanf("%d%d",&a,&b);
mc.G[a - 1][b - 1] = mc.G[b - 1][a - 1] = 0;
}
int ans = mc.maxclique();
printf("%d\n",ans);
for(int i = 1;i <= ans;++i){
printf("%d",mc.res[i] + 1);
if(i == ans) printf("\n");
else printf(" ");
}
}
return 0;
}
4、来一个染色问题:POJ 1129 Channel Allocation
最多 26 广播电台...我还是讲抽象之后的题意吧:最多26个点的无向图,要求相邻的节点不能染成同一个颜色。问最少须要多少颜色染全然部的顶点
利用上面提到的结论:图的染色问题中。最少须要的颜色的数量=最大团点的数量,建图,跑最大团就可以,另外。这题还须要构造解