graph's connected components

题目描述

Given an integers set of m integers,each integer is in the range [0, 2n-1]. 
A graph is build on the following constraints: if integers X and Y satisfy X&Y=0 (& is bitwise AND operation),X and Y are connected by an undirected edge.Please help PIPI count the number of connected components in the graph! 

输入

Input contains multiple test cases. 
Each test case starts with a number n(0 <= n <= 22) and m(1<=m<=2n) . 
The next line contains m different integers: a1,a2...am ,each integer 0<=ai <2n. 

输出

For each case,print connected components for each group of input data.

样例输入

2 3
1 2 3
5 5
5 19 10 20 12
5 6
5 19 10 20 12 0

样例输出

2
2
1

分析:

       我一开始做的是弱化版,上面的题目不变,只是0 <= n <= 12,这是个简单的数连通分量的问题,代码如下:

#include<bits/stdc++.h>
 
using namespace std;
 
int n, m;
vector<vector<int>> adjList;
map<int, int> index_m;
map<int, bool> vis;
 
void dfs(int cur){
    vis[cur] = true;
    int k = index_m[cur];
    for(int i = 0; i < adjList[k].size(); i++){
        if(!vis[adjList[k][i]])
            dfs(adjList[k][i]);
    }
}
 
int main(){
    while(cin >> n >> m){
        adjList.clear();
        index_m.clear();
        int u;
        for(int i = 0; i < m; i++){
            scanf("%d", &u);
            adjList.emplace_back(vector<int> {u});
            index_m[u] = i;
            vis[u] = false;
        }
        //create map
        for(int i = 0; i < m - 1; i++){
            for(int j = i + 1; j < m; j++){
                if((adjList[i][0] & adjList[j][0]) == 0){
                    adjList[i].push_back(adjList[j][0]);
                    adjList[j].push_back(adjList[i][0]);
                }
            }
        }
        //dfs
        int cnt = 0;
        for(int i = 0; i < m; i++){
            if(!vis[adjList[i][0]]){
                dfs(adjList[i][0]);
                cnt++;
            }
        }
        cout << cnt << endl;
    }
    return 0;
}

       这个加强版n的范围是0到22,我用上面的代码总是超时20%,无奈求教大佬,给我了一份答案,然后看懵了。先贴一下答案:

#include<bits/stdc++.h>
  
using namespace std;
  
const int N = 1 << 22;
int A[N], n, m, tp;
bool vis[N], ok[N];
  
void dfs(int now){
    if(ok[now]) return;
    ok[now] = 1;
    if(vis[now]) dfs(now ^ tp);
    int x = now, t = now - (x & -x);
    while(1){
        if(!ok[t]) dfs(t);
        if(!x) break;
        x -= (x & -x);
        t = now - (x & -x);
    }
}
  
int main(){
    while(cin >> n >> m){
        memset(vis, 0, sizeof(vis));
        memset(ok, 0, sizeof(ok));
        for(int i = 0; i < m; i++){
            cin >> A[i];
            vis[A[i]] = 1;
        }
        if(!n){
            cout << "1" << endl;
            return 0;
        }
        tp = (1 << n) - 1;
        int ans = 0;
        for(int i = 0; i < m; i++){
            if(!ok[A[i]]){
                ok[A[i]] = 1;
                dfs(A[i] ^ tp);
                ans++;
            }
        }
        cout << ans << endl;
    }
    return 0;
} 

       这段代码并没有像我一直在考虑用什么数据结构存储图一样,它压根儿就没存图。后来我抛开代码,从题目分析,如果不存图的话,我就应该对于每个顶点,找到能和它连通的顶点。该题就是通过搜索来找可能的顶点,简单来说就是搜索 + 搜索!第一个搜索用来搜连通分量,第二个搜索用来搜可能连通的点。第二个搜索利用二进制的特点,按位运算,大大提高了效率。不过太难理解了。

       我自己取了一个数,令A[i] = 00100110模拟了一下程序。

       然后我通过输出,打印了一个样例的运行过程:

#include<bits/stdc++.h>

using namespace std;

const int N = 1 << 22;
int A[N], n, m, tp;
bool vis[N], ok[N];

void dfs(int now, int depth){
	bitset<8> bs0(now);
	for(int i = 0; i < depth; i++) cout << "\t";
	cout << "----------------begin--------------" << endl;
	for(int i = 0; i < depth; i++) cout << "\t";
	cout << "now: " << now  << " --> " << bs0 << endl;
	if(ok[now]) return;
	ok[now] = 1;
	if(vis[now]){
		for(int i = 0; i < depth; i++) cout << "\t";
		cout << "dfs start:" << (now ^ tp) << endl;
		dfs(now ^ tp, depth + 1);
	}
	int x = now, t = now - (x & -x);
	while(1){
		bitset<8> bs(t);
		for(int i = 0; i < depth; i++) cout << "\t";
		cout << "t: " << t << "  -->  " << bs << endl;
		if(!ok[t]) dfs(t, depth + 1);
		if(!x) break;
		x -= (x & -x);
		t = now - (x & -x);
	}
	for(int i = 0; i < depth; i++) cout << "\t";
	cout << "----------------end--------------" << endl;
}

int main(){
	while(cin >> n >> m){
		memset(vis, 0, sizeof(vis));
		memset(ok, 0, sizeof(ok));
		for(int i = 0; i < m; i++){
			cin >> A[i];
			vis[A[i]] = 1;
		}
		if(!n){
			cout << "1" << endl;
			return 0;
		}
		tp = (1 << n) - 1;
		int ans = 0;
		for(int i = 0; i < m; i++){
			if(!ok[A[i]]){
				ok[A[i]] = 1;
				cout << "---------------------------" << endl;
				cout << "A[i]: " << A[i] << "  A[i] ^ tp: " << (A[i] ^ tp) << endl;
				dfs(A[i] ^ tp, 0);
				cout << "---------------------------" << endl;
				ans++;
			}
		}
		cout << ans << endl;
	}
	return 0;
} 

 

 

       差不多理解了这段代码的意思,本质在上面已经说了,就是在搜索基础上的搜索。只有一个递归函数,既完成了访问的任务,又完成了查找可能连通的顶点的任务。只不过这些位运算让人看得云里雾里。首先vis数组用来表示某个顶点是否存在,ok数组用来表示是否访问。(这名字起的有点反人类,增加了我的理解成本)主函数中dfs传入的参数A[i] ^ dp,实质上就是用来找所有和A[i]相与为0的数的。要保证的是,已经为0的位置不可改变,为1的位置修改成0。dfs函数中,while函数前有一个是否递归的判断,其实判断的就是某个数now是否也是m个整数中的一个,如果是,对其遍历。然后就是while循环,x & -x可以得到最低位为1的数字,x -= (x & -x)依次使每个“1”化为“0”,在while循环里的递归,每递归一层,就多化一个“1”为一个“0”。找出所有可能的数。

eg(举个例子方便理解):

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值