• 团:是一个点集,满足,任意两点有边相连

  • 最大团:点集大小最大的团。

  • 极大团:首先是个团,其次不在团上的点,加入点集不会构成新团。

  • 体会一下,团 \(\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\) 孩子错了)这不对啊,多个的话相当于两个集合,又交的部分有不交的部分,我们又不能说之选交的部分,那有啥优化。行,本文结!
posted @ 2025-04-23 08:01  LUHCUH  阅读(51)  评论(0)    收藏  举报