zoukankan      html  css  js  c++  java
  • 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)本身是一个团,那么对于任意一个点(jin[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)),证明复杂度就是二项式定理逆过来做就可以了.

  • 相关阅读:
    PMP工具与技术篇--4.4.1-1 储备分析
    PMP--4.4 规划成本管理--成本管理计划
    PMP--4.3.4-2 进度基准
    PMP工具与技术篇--4.3.4-1 关键路径分析
    PMP--4.3.4-1 项目进度计划
    pip超时问题解决
    BurpSuite插件_sqlipy
    文件上传漏洞
    SSL安全评估工具
    子域名爆破工具
  • 原文地址:https://www.cnblogs.com/HotPants/p/14224975.html
Copyright © 2011-2022 走看看