zoukankan      html  css  js  c++  java
  • CF 986C AND Graph

    题目大意

    (m) 个互不相同的非负整数 (a_1,a_2,dots,a_n) , 若 (a_i land a_j = 0) ,则在 (i)(j) 之间连一条无向边,问能形成多少个连通块。
    其中 (1 leq n leq 22)(1 leq m leq 2^n)(0 leq a_i leq 2^n - 1)

    题解

    考虑到对 (a_i) 每一位取反得到 (a_i') ,我们设 (a_i')(1) 的二进制位组成的集合为 (T) , 设 (a_j)(1) 的二进制位组成的集合为 (S)
    (a_i land a_j = 0) ,则 (T in S)
    我们用 (vis(a_i)) 来记录 (a_i) 是否被搜到过。
    所以我们可以从 (1)(n) 枚举 (a_i) ,若 (a_i) 未被搜到,则证明有一个新的连通块,首先让答案 (+1)
    然后进行一次 BFS ,枚举 (a_i) 的对应子集所对应的 (a_j) ,若 (a_j) 未被搜到,则加入队尾并修改 (vis(a_j)) 的值。(不会枚举子集的见底)
    然后一直取出队头,重复这个过程。
    (为什么不用 DFS ? 因为听说会爆栈,所以用 BFS 来做。 )
    然而,众所周知,这样枚举子集的时间复杂度是 (O(3^n))(证明见底) ,已经T飞了。所以我们不能这样做。
    我们先将枚举到的未被搜到 (a_i) 取反得到 (a_i') 加入队列,然后进行 BFS 。
    对于每一个队头 (cur) ,如果该队头是 (m) 个数之一,那么就对其取反得到 (cur') ,插入队尾并修改 (vis(cur')) 的值。
    然后对于每个 (cur) ,先枚举 (2^0,2^1,2^2,dots,2^{n-1}) ,若 (cur - 2^k) 未被搜索到,则将其插入队尾并修改 (vis(cur - 2^k)) 的值。
    为什么出现为 (m) 个数之一的 (cur) , 要对 (cur)(cur') 都进行 BFS ?因为 (cur) 即可能作为搜到的可以连边的数,也可以作为继续减 (2^k) 以搜索子集的数。
    显然时间复杂度为 (O(n2^n)) ,比上面的优秀很多。
    看不懂上面解释的话还是看代码吧,自己模拟一下应该就可以理解。

    #include <cstdio>
    
    #define MAX_M ((1 << 22) + 5)
    
    int n, m;
    int a[MAX_M];
    bool b[MAX_M], vis[MAX_M];
    int q[MAX_M], l, r;
    int ans;
    
    int Read()
    {
    	int res = 0;
    	char ch = getchar();
    	while (ch < '0' || ch > '9') ch = getchar();
    	while (ch >= '0' && ch <= '9') res = res * 10 + ch - '0', ch = getchar();
    	return res;
    }
    
    int main()
    {
    	n = Read();
    	m = Read();
    	for (int i = 1; i <= m; ++i)
    	{
    		a[i] = Read();
    		b[a[i]] = 1;	
    	}
    	int cur;
    	const int lim = (1 << n) - 1;
    	for (int i = 1; i <= m; ++i)
    	{
    		if (vis[a[i]]) continue;
    		++ans;
    		l = r = 1;
    		q[l] = lim ^ a[i];
    		vis[a[i]] = vis[q[l]] = 1;
    		while (l <= r)
    		{
    			if (b[q[l]])
    			{
    				cur = lim ^ q[l];
    				if (!vis[cur])
    				{
    					q[++r] = cur;
    					vis[cur] = 1;
    				}
    			}
    			cur = q[l++];
    			for (int j = 1; j <= lim; j <<= 1)
    			{
    				if (!(cur & j) || vis[cur - j]) continue;
    				vis[cur - j] = 1;
    				q[++r] = cur - j;
    			}
    		}
    	}
    	printf("%d", ans);
    	return 0;
    } 
    



    枚举子集:

    for (int T = S; T; T = T - 1 & S)
    

    不解释,模拟一下就懂了。

    时间复杂度证明:
    考虑一个大小为 (2^n) 的集合 (S)
    (S) 的包含 (0) 个元素的子集有 (C_n^0) 个,每个子集有 (2^0) 个子集。
    (S) 的包含 (1) 个元素的子集有 (C_n^1) 个,每个子集有 (2^1) 个子集。
    拓展得, (S) 的包含 (m) 个元素的子集有 (C_n^m) 个,每个子集有 (2^m) 个子集。
    所以总枚举数量为 (sumlimits_{i=0}^{n} 2^iC_n^i = sumlimits_{i=0}^{n} 2^i imes 1^{n-i} imes C_n^i)
    根据二项式定理的得到上式 (= (2+1)^n = 3^n)
    所以时间复杂度为 (O(3^n))

  • 相关阅读:
    Python读取ini配置文件封装方法
    Python对Selenium调用浏览器进行封装包括启用无头浏览器,及对应的浏览器配置文件
    windows下 apache 二级域名相关配置
    Incorrect integer value: '' for column 'id' at row 1
    [Effective Java 读书笔记] 第二章 创建和销毁对象 第三 四条
    [Effective Java 读书笔记] 第二章 创建和销毁对象 第二条
    [Effective Java 读书笔记] 第二章 创建和销毁对象 第一条
    JAVA ReentrantLock的使用
    Thread类的interrupted方法和isInterrupted方法的区别
    [转]java 为什么wait(),notify(),notifyAll()必须在同步方法/代码块中调用?
  • 原文地址:https://www.cnblogs.com/kcn999/p/13460068.html
Copyright © 2011-2022 走看看