团
团
-
团:是一个点集,满足,任意两点有边相连
-
最大团:点集大小最大的团。
-
极大团:首先是个团,其次不在团上的点,加入点集不会构成新团。
-
体会一下,团 \(\subset\) 极大团 \(\subset\) 最大团。
最大团
- 若我们只想找出最大团的点集数。
- 一种好的办法其实就是直接搜索加减枝。
- 优化1: 自然的我们通过对点编号,排序,钦定每次在团中考虑扩展一个更大的点。(相当于去重)
- 优化2: 我们考虑若枚举到 \(i\) 当前团的大小为 \(cnt\),若 \(n-i+cnt\le best\) 赶快返回
- 我们考虑从大到小一次枚举一个起始点 \(i\),并记录所有起始点在 \(i\) 或比 \(i\) 大的点的 \(best\) 为 \(num_i\)
- 注意到 \(num_{i+1}+1\ge num_{i}\ge num_{i+1}\) 所以有两个优化
- 优化3:若 \(cnt+num_i\le best\) 赶快返回
- 优化4:若是发现答案更优,不用期盼本轮搜索答案会更优秀,直接火速结束当前搜索。
- Maximum Clique
#include <vector>
#include <iostream>
using namespace std;
const int N = 60;
int mp[N][N], num[N], best, n;
bool dfs(int* v, int top, int cnt)
{
if (!top)
{
if (cnt > best)return best = cnt, true;
return false;
}
int to[N], tp;
for (int i = 0;i < top;i++)
{
if (cnt + top - i <= best)break;
if (cnt + num[v[i]] <= best)break;
tp = 0;
for (int j = i + 1;j < top;j++)
if (mp[v[i]][v[j]])to[tp++] = v[j];
if (dfs(to, tp, cnt + 1))return true;
}
return false;
}
int solve()
{
best = 0; int v[N];
for (int i = n;i >= 1;i--)
{
int top = 0;
for (int j = i + 1;j <= n;j++)if (mp[i][j])
v[top++] = j;
dfs(v, top, 1); num[i] = best;
}
return best;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);cout.tie(nullptr);
while (cin >> n && n)
{
for (int i = 1;i <= n;i++) for (int j = 1;j <= n;j++) cin >> mp[i][j];
cout << solve() << '\n';
}
}
极大团(Bron–Kerbosch 算法)
- 首先我们需要维护三个集合,\(R,P,X\) 分别表示,\(R\) 当前选的团,\(P\) 当前可能加入团的点,\(X\) 之前没有加入团的点,到现在依然可以加入团。这里的 \(X\) 只是为了去重。
- 考虑什么时候我们找到了一个极大团,当 \(P=X=\phi\) 也就是当前任意加一个团外的点,都不再是一个团,那么它就是一个极大团。
- 考虑搜索一次选取 \(P\) 中的点执行一下操作。
- 取 \(u\in P\),\(N(u)=\{v|u\to v\}\) 也就是所有 \(u\) 相邻的点的集合。
- 搜索 \(R'=R\bigcup\{u\},P'=P\bigcap N(u),X'=X\bigcap N(u)\)
- 然后就是 \(u\) 能选没选的情况于是,\(P\) 中删除 \(u\) 然后 \(X\) 中加入 \(u\) 然后一直造成影响
- POJ 2989: All Friends
#include <cstring>
#include <iostream>
using namespace std;
const int N = 128 + 10;
const string STR = "Too many maximal sets of friends.";
int n, m, ans, mp[N][N];
struct Box
{
int a[N], tp;
void clear() { tp = 0; }
void add(int u) { a[tp++] = u; }
int back() { return a[tp - 1]; }
void del() { tp--; }
int size() { return tp; }
int* begin() { return a; }
int* end() { return a + tp; }
};
Box R[N], P[N], X[N];
void solve(int d)
{
if (!P[d].size() && !X[d].size())return ans++, void();
const int to = d + 1;
for (int& u : P[d])
{
P[to].clear(); X[to].clear(); R[to].clear();
for (int v : R[d])R[to].add(v);R[to].add(u);
for (int v : P[d])if (mp[u][v])P[to].add(v);
for (int v : X[d])if (mp[u][v])X[to].add(v);
solve(d + 1); X[d].add(u); u = 0; if (ans > 1000)return;
}
}
int solve()
{
ans = 0; P[1].clear(); R[1].clear(); X[1].clear();
for (int i = 1;i <= n;i++)P[1].add(i);
solve(1); return ans;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);cout.tie(nullptr);
while (cin >> n >> m)
{
memset(mp, 0, sizeof(mp));
for (int i = 0;i < m;i++)
{
int a, b; cin >> a >> b;
mp[a][b] = mp[b][a] = 1;
}
solve();
if (ans > 1000) cout << STR << "\n";
else cout << ans << "\n";
}
}
- 然后其实有一个什么优化。
- 考虑从 \(P\) 中选一个元素 \(u\),先尝试加入 \(u\) 若是不能,则把 \(u\) 从 \(P\) 删除加入 \(X\)
- 此时 \(X\) 里面有 \(u\),若是想要 \(X\) 为空,需要团中加入非 \(N(u)\) 的点。
- 若是从 \(P\) 中选择了一个元素 \(v\) 而且 \(v\notin N(u)\) 那早早晚晚需要加入其他 \(w\in N(u)\) 的元素。
- 这就导致算重了,就先选 \(w\) 后选 \(v\), 和先选 \(v\) 后选 \(w\)
- 于是我们干脆把集合 \(P\) 按照队列的顺序访问。取对头为关键元素 \(u\)
- 那么所有 \(P\) 中的其他点 \(v\),若是 \(v\notin N(u)\) 我们先不搜索 \(v\)。那么不搜索了,自然有 \(v\) 不用入 \(X\) 但是 \(v\) 需要保留在 \(P\) 中以便,\(w\) 之后访问。起始写出来就是直接 continue 就对了。
#include <cstring>
#include <iostream>
using namespace std;
const int N = 128 + 10;
const string STR = "Too many maximal sets of friends.";
int n, m, ans, mp[N][N];
struct Box
{
int a[N], tp;
void clear() { tp = 0; }
void add(int u) { a[tp++] = u; }
int back() { return a[tp - 1]; }
void del() { tp--; }
int size() { return tp; }
int* begin() { return a; }
int* end() { return a + tp; }
};
Box R[N], P[N], X[N];
void solve(int d)
{
if (!P[d].size() && !X[d].size())return ans++, void();
const int to = d + 1, tt = *P[d].begin(); // add:
for (int& u : P[d])
{
if (mp[tt][u])continue; // add:
P[to].clear(); X[to].clear(); R[to].clear();
for (int v : R[d])R[to].add(v);R[to].add(u);
for (int v : P[d])if (mp[u][v])P[to].add(v);
for (int v : X[d])if (mp[u][v])X[to].add(v);
solve(d + 1); X[d].add(u); u = 0; if (ans > 1000)return;
}
}
int solve()
{
ans = 0; P[1].clear(); R[1].clear(); X[1].clear();
for (int i = 1;i <= n;i++)P[1].add(i);
solve(1); return ans;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);cout.tie(nullptr);
while (cin >> n >> m)
{
memset(mp, 0, sizeof(mp));
for (int i = 0;i < m;i++)
{
int a, b; cin >> a >> b;
mp[a][b] = mp[b][a] = 1;
}
solve();
if (ans > 1000) cout << STR << "\n";
else cout << ans << "\n";
}
}
- 诶,那我为什不能选择多个关键点?我去试试(一段时间 \(\to\) 孩子错了)这不对啊,多个的话相当于两个集合,又交的部分有不交的部分,我们又不能说之选交的部分,那有啥优化。行,本文结!

浙公网安备 33010602011771号