独立集问题

IOI2017国家集训队论文

定义

独立集问题有多种形式。为了方便描述,以下给出一些定义。

定义 2.1. 对于无向图 \(G = (V, E)\) 和点 \(u, v ∈ V\),若 \((u, v) ∈ E\),则称 \(u, v\) 相邻(adjacent);定义点 \(v ∈ V\)邻域(neighborhood)为 \(V\) 中与 \(v\) 相邻的结点集合,记为 \(N(v)\);另外,\(N_G(v)\)表示 \(v\) 在图 \(G\) 中的邻域。

定义 2.2. 点 \(v\)(degree)\(deg(v)\) 定义为 \(N(v)\) 的大小,即 \(deg(v) = |N(v)|\);另外,\(deg_G(v)\) 表示 v 在图 G 中的度。

定义 2.3. 无向图 \(G = (V, E)\) 的一个独立集(independent set)定义为 \(V\) 的一个子集,满足子集中的结点两两不相邻。形式化地,\(I\)\(G\) 的一个独立集,当且仅当 \(I ⊆ V\)\(∀u, v ∈ I, (u, v) \not\in E\)

定义 2.4. 无向图 \(G = (V, E)\) 的一个最大独立集(maximum independent set)是指 \(G\) 中所含结点数 \(|I|\) 最多的独立集 \(I\)

定义 2.5. 无向图 \(G = (V, E)\)独立数(independence number)定义为 \(G\) 的最大独立集 \(I\)
含的结点数 \(|I|\),记为 \(α(G)\)

定义 2.6. 无向图 \(G = (V, E)\)\(S ⊆ V\) 上的导出子图(induced subgraph)定义为以 \(S\) 为点集,两端点都在 \(S\) 内的边为边集构成的图,记为 \(G[S]\)

一般图的独立集问题

目前,解决一般图的大多数独立集相关的问题都不存在多项式时间的算法,只能用复杂度较优的指数级算法。
事实上,已有不少理论复杂度十分优秀的求图的最大独立集的算法,能够快速计算出上百阶的无向图的最大独立集,但这些算法实现往往过于复杂,难以应用到信息学竞赛中。笔者选择了一些相对高效又较易于实现的算法进行了研究。

基于极大独立集搜索的独立集算法

最朴素的搜索算法非常简单:用深度优先搜索枚举 \(V\) 的子集 \(I ⊆ V\),即按一定顺序枚举每个点 \(v ∈ V\) 是否属于 \(I\),一旦存在 \((u, v) ∈ E\) 使得 \(u, v ∈ I\),就回溯。输出枚举的所有独立集 \(I\) 中,\(|I|\) 最大的一个。该算法的复杂度为 \(O(2^nm)\),效率太低。

朴素的搜索算法效率太低,有没什么好的方法来优化呢?考虑极大独立集。不少有关独立集的组合优化问题都可以只考虑极大独立集,最大独立集问题就是这样一个例子:
定理 1:最大独立集都是极大独立集。

如果直接搜索极大独立集的话,效率是很低的,因为会搜到很多不是极大的独立集。
例如当 \(G\)\(n\) 阶零图(没有边的图)时,显然 \(V\)\(G\) 的唯一的极大独立集,然而朴素的搜索枚举了某个结点不属于极大独立集时,尽管不可能搜出极大独立集,但算法还会继续搜索下去,浪费了大量时间。
我们有一个优化策略:根据以下定理进行优化。
定理 2:对于极大独立集 \(I\),不存在点 \(v\) 使得它和其邻域 \(n(v)\) 上所有点都不在 \(I\) 上。

那么我们引入 Bron 算法:
令目前确定在独立集内的点集为 \(R\),可以加入独立集内(待定)的点集为 \(P\)钦定不可以加入独立集内的点集(就是已经搜索完毕的点集)为 \(X\)。那么对于一个状态,\(P\cup X = V'\)\(R\) 只是最后用于增加点的点集;\(V'\) 是还可供选择的点集。这点在之后的算法中可以发现。

\(\mathtt{Bron}(R,P,X)\) 返回的是当前状态下的最大独立集大小。

在函数内部,考虑新增一个加入独立集的点。对于任意 \(P \cup X\) 中节点 \(u\),都有 \(P \cup (u \cup N(u))\) 中必然有一个点被选择。(由于任何已经被选择的点都已经把自己和邻域排除在 \(P \cup X\) 之外,所以不存在已经有点被选择的情况。继续搜索时,枚举这一个集合内的点,那么自然想到选取元素个数最少的一个 \(u\)

选出 \(u\) 之后,对于所有 \(P \cup (u \cup N(u))\) 内节点 \(v\),执行如下两个步骤:

  • 求出选 \(v\) 的答案,也就是 \(\mathtt{Bron}(R \cup \{v\}, P - (v \cup N(v)), X - (v \cup N(v)))\)
  • 钦定不选 \(v\)(不会重复计算),\(P = P - \{v\}, X = X \cup \{v\}\)。(这样的话,下一次选 \(u\) 邻域内节点也不会再选到 \(v\),因为 \(u\) 邻域上点的邻域不一定包含 \(v\),不这样做可能会选到)

递归终点:
\(P = X = ∅\) 的时候,所有可选节点都在 \(R\) 中,用 \(R\) 更新答案。\(P = \emptyset\) 的时候,如果 \(X \neq \emptyset\),那么令 \(k \in X\),那么它的邻域内没有选择任何一个节点,否则这个点选择过的话会把 \(k\) 删掉。并且你无法再添加任何节点了。这时候你肯定没有搜索到极大独立集,直接返回即可。
\(X = \emptyset\) 的时候,\(P\) 一定也为 \(\emptyset\)

时间复杂度:递归调用层数 \(O(3^{\frac{n}{3}})\),证明很复杂,结论背下来即可。

另外,一张图的极大独立集也是 \(O(3^{\frac{n}{3}})\) 的。造 \(k\)\(k\) 元环可以达到上界,并且 \(k = 3\) 的时候,达到最大上界(实数范围上界在 \(k = e\) 取到)。

由于该算法基于极大独立集,也可以求解最大正权独立集。

对于随机图:
image

CF1767E

\(n \le 40\)

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define f(i, a, b) for(int i = (a); i <= (b); i++)
#define cl(i, n) i.clear(),i.resize(n);
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int inf = 1e9;
void cmax(int &x, int y) {if(x < y) x = y;}
void cmin(int &x, int y) {if(x > y) x = y;}
int n,m;
int c[300010],x[44];
int g[44];  //状压
void add(int x, int y) {g[x] |= (1ll << y); g[y] |= (1ll << x);}
int ans=0,sum=0;
void print(int t){
    string s;cin>>s;
    f(i,0,m-1){s+=char((t&1)+'0');t>>=1;}
    cout<<s<<endl;
}
void bron(int R,int P,int X){
    if(P==0){
        int tmp=0;
        f(i,0,m-1)if((R>>i)&1)tmp+=x[i]; 
        cmax(ans,tmp);  
        return;
    }
    int mn=inf,ch=0;
    f(i,0,m-1){
        if((P>>i)&1 || (X>>i)&1){
            int gs=(P&(g[i]|(1ll<<i)));
            int nt=__builtin_popcountll(gs);
            if(nt != 0 && nt<mn){mn=nt;ch=i;}
        }
    }
    int ly = (P&(g[ch]|(1ll<<ch)));
    f(i,0,m-1){
        if((ly >> i) & 1) {
            int lx = (g[i]|(1ll<<i));
            int flx = ~(lx);
            bron(R | (1ll << i), P & flx, X & flx);
            P -= (1ll<<i);
            X |= (1ll<<i);
        }
    }
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
    //time_t start = clock();
    //think twice,code once.
    //think once,debug forever.
    cin>>n>>m;
    f(i,0,n-1){cin>>c[i];c[i]--;}
    f(i,0,m-1){cin>>x[i];sum+=x[i];}
    f(i,0,n-2){add(c[i],c[i+1]);}
    int R=0,P=0,X=0;
    f(i,0,m-1){
        if(c[0]==i||c[n-1]==i||((g[i]>>i)&1));
        else P|=(1ll<<i);
    }
    f(i,0,m-1)g[i]&=P;
    bron(R,P,X);
    cout<<sum-ans<<endl;
    //time_t finish = clock();
    //cout << "time used:" << (finish-start) * 1.0 / CLOCKS_PER_SEC <<"s"<< endl;
    return 0;
}

trick:可以用 R &= ~P 来执行 \(R -= P\)

基于折半搜索的独立集算法

考虑折半查找的思想。
如果把一整张图分成两个部分,前 \(n/2\) 个点和后 \(n/2\) 个点。分别将这两个点集记为 \(mask_1, mask_2\)。对于 \(mask_1\)\(mask_2\) 的每个子集 \(S\),状态压缩处理出 \(G[S]\) 的最大独立集。
然后考虑枚举 \(mask_1\) 里选了哪些点,对这些点的邻域取并集之后,没有取到的点(在 \(mask_2\) 中)就是可选择的点集。这时候我们需要查询这个点集的所有子集的最大独立集的最大值。总共 \(O(2^{n/2})\) 次查询。怎么办?
使用高维前缀和处理子集信息查询问题。
想象高维空间上的几何体得到,令 \(m = n/2\),那么 \(m\) 维空间中,如果做前缀和,那么 \(s_{x_1,...,x_m}\) 就是 \(x_1...x_m\) 的所有子集的权值和。
这个前缀和,除了容斥求解,还可以这样做:对每个维度分别求一次前缀和。
令值域为 \([1,k]\),那么时间复杂度 \(O(m \times k^m)\) 可以预处理所有集合的子集和。其中 \(k^m\) 是高维空间容量。

for(int i=1;i<=n;++i)
{
    for(int j=1;j<=m;++j)
    {
        for(int k=1;k<=p;++k)
        {
            a[i][j][k]+=a[i-1][j][k];
        }
    }
}
for(int i=1;i<=n;++i)
{
    for(int j=1;j<=m;++j)
    {
        for(int k=1;k<=p;++k)
        {
            a[i][j][k]+=a[i][j-1][k];
        }
    }
}
for(int i=1;i<=n;++i)
{
    for(int j=1;j<=m;++j)
    {
        for(int k=1;k<=p;++k)
        {
            a[i][j][k]+=a[i][j][k-1];
        }
    }
}

对于子集求和问题,\(k=2\)\(0/1\) 分别表示选不选。

for(int i = 1; i <= m; i++) {
    for(int j=0;j<(1<<w);++j)//求每个维度的前缀和
    {
        if(j&(1<<i))s[j]+=s[j^(1<<i)]; 
    }
}

总时间复杂度 \(O(2^{n/2} \times n^2)\)。可以处理负权。

树和基环树的最大(权)独立集

树的最大独立集问题,不能黑白染色取黑或者白,因为只是保证邻域内必须选一个,没说相邻的也要选。

考虑树形 dp。\(dp_{i,0/1}\)\(i\) 个选不选,子树内最大独立集。

基环树,如下图,环上面每个点是一棵树的根。
image

考虑先把每棵树的答案算出来,然后环上的点标号,\(f_{i, 0/1,0/1}\) 表示第 \(i\) 个点选不选,第一个点选不选。
然后最后一个点注意一下就好了。

posted @ 2022-12-19 16:09  OIer某罗  阅读(412)  评论(0编辑  收藏  举报