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\)),证明复杂度就是二项式定理逆过来做就可以了.

posted @ 2021-01-03 10:53  随处可见的阿宅  阅读(202)  评论(0编辑  收藏  举报