题目描述
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(举个例子方便理解):