Max Independent Set

Max Independent Set

此题还是比较巧妙的,值得花点时间。

最大独立集是指图上最大的节点集,内部没有节点间的边。 首先最大独立集是最大团(max clique)的互补概念,补图(没边的节点间加边, 去掉所有现有边)的最大clique,就是原图的最大独立集。所以我们首先把问题转化为求补图上的最大clique, 这个是个更容易理解的问题。

因为N=40N=40,所以想到meet in the middle的办法,但此题的难点,在于在求出前后一半中的max clique之后, 怎样进行合并。简单求出前后半个图各一个max clique肯定是不行的,因为最后整体的max clique不一定是这两个clique 组合而成的。所以我们需要有更多的中间结果。

在纸上画画可以发现,一种合并办法是:

这里SS是左侧图中的一个clique,设HHSS在全图中的“公共邻居”,就是HH集合中每一点都有到SS每个点的边。 那么如果我们可以找到HH中最大的clique SS',则SS'就是与SS可以合并的右侧图的clique了。因为 SSSS'都是clique,所以内部全部两两有边,而“公共邻居”的定义,使得他们互相间也两两有边,所以合并起来后也是 clique,而对于SS来说,容易证明SS'是在右侧图中能找到的最大clique。因此,对所有SS求合并后的最大clique, 就是题目解了。

公共邻居的求法容易,见下方程序中common[]

另外我们还需要快速求解在右侧子图中,一个节点集内最大的clique,设为f[H]f[H],其中f[]f[]HH都用bitmask表示。 这个用递推的方式来做,任选HH中的一个节点kkAkA_kkk的邻居集合,则有:

f[H]=popmax(f[Hk],f[AkH]{k})f[H] = \mathtt{popmax} (f[H \setminus k], f[A_k \cap H] \cup \{k\})

popmax()\mathtt{popmax}()是选两个bitmask中, 11的位多的数返回。注意AkA_k中不包含kk, 所以AkHA_k \cap H一定是HH的真子集,因此用这个可以递推计算。具体见代码中generate()函数。

另外为了加速运算和简化代码,使用了一些位运算:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pi;
 
int n, m;
int gr[45][45];
ll adj[45];
vector<ll> ml, mr;      // ml[i],subset i中最大的clique
vector<ll> common;      // common[i]: 左侧subset i对应的共同邻居
ll ans;
 
ll popmax(ll a, ll b) {
    return __builtin_popcountll(a) < __builtin_popcountll(b) ? b : a;
}
 
void generate(int l, int r, vector<ll> &f) {
    f.resize(1 << (r-l+1), 0);
    for (int i = 1; i < (1 << (r-l+1)); i++) {
        int d = i & (-i);           // i中最低位的1
        int c = __builtin_ctzll(d); // d后面有几个0
        f[i] = popmax(f[i ^ d], f[i & (adj[c + l] >> l)] | d);
    }
}
 
int main() {
    scanf("%d %d", &n, &m);
    for (int i = 0; i < m; i++) {
        int u,v;
        scanf("%d %d", &u, &v);
        gr[u][v] = gr[v][u] = 1;
    }
    for (int i = 0; i < n; i++)    // 补图
        for (int j = 0; j < n; j++) 
            if (!gr[i][j] && i != j)
                adj[i] |= 1ll << j;
 
    generate(0, n/2-1, ml);         // 生成左侧所有clique
    generate(n/2, n-1, mr);         // 生成右侧所有clique
    
    common.resize(1 << (n/2), 0);
    common[0] = (1ll << n) - 1;
    for (int i = 1; i < (1 << n/2); i++) {  // 哪些节点是左侧子集i的公共邻居
        int d = i & (-i);
        common[i] = common[i - d] & adj[__builtin_ctzll(i)];
    }
 
    // meet in the middle
    for (int i = 0; i < (1 << n/2); i++)
        ans = popmax(ans, (mr[common[i] >> (n/2)] << (n/2)) | ml[i]);
    
    printf("%d\n", __builtin_popcountll(ans));
    for (int i = 0; i < n; i++)
        if (ans & (1ll << i))
            printf("%d\n", i);
 
    return 0;
}