atcoder abc187F Close Group
原题链接:abc187F Close Group
题目大意
给定一个\(n\)个点,\(m\)条边的无向图,题目中保证没有重边.点集标记为\(1,2,3,....n\).删去任意多条边,求最少能把整个图划分成若干个满足以下条件的连通分量.
条件:对于连通分量中任意两个点\(a,b\)都有\(a,b\)之间有一条直接相连的边.
注意:求的是划分的连通分量的个数,不是删去最少的边.
数据范围
\(1 \leq n \leq 18\)
\(1 \leq m \leq N * (N - 1) / 2\)
数据中保证没有重边
思路
由于点的数量非常少,不难往搜索或者状压的路线上想,搜索不太好搞分割,考虑状压DP:
首先考虑状态的设计,因为整个图的状态是由多个符合题意的连通分量(下称"团")构成的,不难想到先把各个点集划分的方案求出来,再合并出更大的点集,由此可以想到一个比较老的套路:枚举子集的状压DP.
注意往下设计的时候为了减少一格的位置,这里把点的标号往前挪了一位,即从\(0\)开始.
状态:\(f[i]\)表示选出的点集状态是\(i\)时,划分的团的个数集合中的最小值.(\(i\)位取真表示这个点被选入点集,否则表示不在点集中).
入口:\(f[0] = 0\)显然没点的时候不需要划分
\(f[i] = 1\)当点集\(i\)恰好是一个团的时候,划分的值当然是\(1\).关于这一步情况,我们放到转移中考虑.
转移:
- 点集\(i\)本身是一个团,那么对于任意一个点\(j\in[0,n-1]\)来说,他都必须要能走到集合\(i\)里包含的任何一个点.不妨跟状压DP一样处理,对每个点\(u\)预处理出一个数,表示\(u\)可以到达的点的集合,记作\(connect[u]\).在具体判断的时候,我们只需要关注,对于\(i\)里面包含的每个点来说是否都在\(connect[j]\)中出现了,那么把两个数与起来,不在\(i\)中出现的点,自然会被处理成\(0\),对于在\(i\)里出现了的点,假如\(connect[j]\)里也出现了,那么这一位自然也是\(1\)否则就是\(0\),所以判据只需要写
if ((connect[j] & i) == i)
即表示\(j\)可以走到\(i\)集合里包含的任意一个点. - 考虑把\(i\)集合分割成两个子集,这里就是一个比较经典的套路:枚举一个集合\(S\)的所有子集,这一步由于过于套路放到后面单独简单说一下,这里就不多说了:分割的一个子集是\(s\)那么另一个就是\(i-s\),状态转移方程就是\(f[i] = min(f[i],f[s] + f[i - s])\).
出口:显然是选取所有点的集合\(f[(1 << n) - 1]\)
代码
#define _CRT_SECURE_NO_WARNINGS
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 18;
int connect[N],f[1 << N];
int main()
{
int n,m;scanf("%d%d",&n,&m);
for(int i = 0;i < n;++i) connect[i] |= (1 << i);
for(int i = 0;i < m;++i)
{
int u,v;scanf("%d%d",&u,&v);
--u;--v;
connect[u] |= (1 << v);
connect[v] |= (1 << u);
}
f[0] = 0;
for(int i = 1;i < (1 << n);++i)
{
f[i] = 1e5+7;
bool indp = 1;
for(int j = 0;j < n;++j)
if((i >> j & 1) && ((connect[j] & i) != i))
indp = 0;
if(indp) f[i] = 1;
for(int s = i;s;s = (s - 1) & i)
f[i] = min(f[i],f[s] + f[i - s]);
}
printf("%d",f[(1 << n) - 1]);
return 0;
}
关于子集枚举
对于一个二进制表示的集合\(S\)来说,他的子集\(s'\)一个比较简单的想法就是不断地让\(S\)减一,并且判断是否没有出现在\(S\)但是出现在\(s'\)中的点,如果存在的话就说明不是自己的子集,这里有个比较简单的优化处理,就是直接减一的同时相与,这样可以快速挖掉上面所说的违反规则的点,并且相与之后不会增大值,每次减一就可以保证子集全部枚举到了.时间复杂度整体是\(O(3^n)\)的(这里要包含第一步枚举集合\(i\)),证明复杂度就是二项式定理逆过来做就可以了.