最大团解析及用处
题目:
求一张图中。最多的点集的最大个数。称为最大团。
////////////////////////////////////////////////
下面是转载他人的博客: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 ",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 ",ans); for(int i = 1;i <= ans;++i){ printf("%d",mc.res[i] + 1); if(i == ans) printf(" "); else printf(" "); } } return 0; }
4、来一个染色问题:POJ 1129 Channel Allocation
最多 26 广播电台...我还是讲抽象之后的题意吧:最多26个点的无向图,要求相邻的节点不能染成同一个颜色。问最少须要多少颜色染全然部的顶点
利用上面提到的结论:图的染色问题中。最少须要的颜色的数量=最大团点的数量,建图,跑最大团就可以,另外。这题还须要构造解