并查集, 连通分量
1107
// 1.如果说“error: ‘scanf’ was not declared in this scope”:
// 具体的解决: include "stdio.h"
// 统一解决: include <bits/stdc++.h>
// 2.并查集:
// (1)father数组: 大小--节点的个数。 初始化--每个节点是自己的父节点。
// (2)combine数组: 查看两个参数的父集是否相同, 不同则赋值。
// (3)find: 如果父集不是自己就一直循环。 路径压缩
// (4)解决的问题:判断连通分支个数
// 3.scanf是可以忽略" "的
// 4.注意把==和=写错的错误
//核心: 并查集----反向建树 + 连通分支个数 + 每个连通分支节点数
//1.并查集的反向建树:
// (1)即输入数据不是每一波, 在一个联通分支下, 而是有相同叶子节点的在一个联通分支下
// (2)解决方法:
// 方法一:
// if(indexs[index]==0)indexs[index]=i;
// combine(indexs[index], i);
// 方法二:
// 利用一个二维向量vector<vector<int> >indexs, 反向建树即可
//2.并查集寻找根节点:
// (1)准备roots[节点数]----就像数组型树一样:
// 1)并查集的题目, 很多数据给出的节点都是连续的, 并且数量是已知的: vector<int>roots(节点数)
// 2)不连续,总个数位置: myHash: 用大vector 或者 map, 但是map的缺点就是, 不能排序
//3.并查集获得每个联通分支的个数: 在寻找根节点的时候即可找到
// #include "stdio.h"
#include <bits/stdc++.h>
#include <vector>
#include <algorithm>
using namespace std;
vector<int>isRoot;
vector<int>father;
vector<int>hobby(1001, 0);
int find(int x) {
int son, tmp;
son = father[x];
while (father[x] != x)x = father[x];
while (son != x) {
tmp = father[son];
father[son] = x;
son = tmp;
}
return x;
}
bool cmp(int a, int b) {
return a > b;
}
void combine(int a, int b) {
int fa1 = find(a);
int fa2 = find(b);
if (fa1 != fa2)father[fa2] = fa1;
}
int main() {
//输入 和 初始化
int n;
scanf("%d", &n);
isRoot.resize(n + 1);
father.resize(n + 1);
fill(isRoot.begin(), isRoot.end(), 0);
for (int i = 1; i <= n; i++) {
father[i] = i;
}
for (int i = 1; i <= n; i++) {
int k;
scanf("%d:", &k);
//printf("K: %d ", k);
for (int j = 0; j < k; j++) {
int h;
scanf("%d", &h);
if (hobby[h] == 0)hobby[h] = i;
combine(hobby[h],i);
}
}
//
for (int i = 1; i <= n; i++) {
isRoot[find(i)]++;
}
int cnt=0;
for (int i = 1; i <= n; i++) {
if (isRoot[i] != 0)cnt++;
}
sort(isRoot.begin(), isRoot.end(), cmp);
printf("%d\n", cnt);
for (int i = 0; i < cnt; i++) {
printf("%d", isRoot[i]);
if (i != (cnt - 1))printf(" ");
else printf("\n");
}
return 0;
}
并查集, 联通分量, 节点数据统计
1114
//1.iterator的用法: 不能有两个一样名字的
//2.scanf没写“&”在编译时无法检查出
//3.scanf和printf的数据类型必须是对的, 否则会出错, 并且数字也很奇怪
//4.scanf和输入的数据类型没对上也会出错(比如是整数, 用%f接收不行)
//5.并查集的father需要直接用下标来表示关系, 所以通常是一个大的数组
//6.map迭代器访问元素用first,second
//7.两个整数相除想得到浮点数,不仅要等式左边为浮点数, 某一个数也要*1.0变成浮点数
//8.vector, set, map用的时候要想清楚了: 各自缺点:1.无法按值取 2.3.无法下标访问, 遍历只能迭代器
//9.某个对象所属变量太多的话, 使用struct, 用好初始化函数name():a(1),b(0){}
//10.并查集在累积数据时, 直接使用父集,而不是父节点。并且这个操作在combine函数(父节点在上次合并的时候算访问过)
//11.学会sort。cmp函数的使用
//12.并查集comnine函数认谁做一直的父节点也是一个点
//13.printf输出的格式要求在%之后, 并且, %0xd, %.xf.
#include <stdio.h>
#include <vector>
#include <set>
#include <map>
#include <algorithm>
using namespace std;
struct person {
int id, num, ma, fa, set, area;
double setAvg, areaAvg;
vector<int>childs;
person() :num(1), set(0), area(0),ma(-1),fa(-1) {}
};
vector<int>father(10001);
map<int, person>persons;
vector<person>result;
//set<int>ids;
bool cmp(person a, person b) {
if (a.areaAvg != b.areaAvg)
return a.areaAvg > b.areaAvg;
else return a.id < b.id;
}
int find(int x) {
int son = father[x];
while (father[x] != x)x = father[x];
while (son != x) {
int tmp = father[son];
father[son] = x;
son = tmp;
}
return x;
}
void combine(int a, int b) {
int fa1 = find(a);
int fa2 = find(b);
if (fa1 < fa2) {
father[fa2] = fa1;
persons[fa1].num += persons[fa2].num; //?
persons[fa1].set += persons[fa2].set;
persons[fa1].area += persons[fa2].area;
}
else if (fa1 > fa2) {
father[fa1] = fa2;
persons[fa2].num += persons[fa1].num; //?
persons[fa2].set += persons[fa1].set;
persons[fa2].area += persons[fa1].area;
}
}
int main() {
int n;
scanf("%d", &n);
for (int i = 0; i < father.size(); i++) father[i] = i;
for (int i = 0; i < n; i++) {
person tmp;
int k, ch;
scanf("%d %d %d %d", &tmp.id, &tmp.ma, &tmp.fa, &k);
//ids.insert(tmp.id);
for (int j = 0; j < k; j++) {
scanf("%d", &ch);
tmp.childs.push_back(ch);
//combine(id, ch);
}
scanf("%d %d", &tmp.set, &tmp.area);
persons[tmp.id] = tmp;
}
检查输入
//map<int, person>::iterator it1;
//for (it1 = persons.begin(); it1 != persons.end(); it1++) {
// printf("id: %d ma: %d fa: %d chsize: %d se: %d ar: %d\n", it1->second.id, it1->second.ma, it1->second.fa, it1->second.childs.size(), it1->second.set, it1->second.area);
//}
//并
map<int, person>::iterator it2;
for (it2 = persons.begin(); it2 != persons.end(); it2++) {
if (it2->second.ma != -1) {
combine(it2->second.id, it2->second.ma);
}
if (it2->second.fa != -1) {
combine(it2->second.id, it2->second.fa);
}
for (int j = 0; j < it2->second.childs.size(); j++) {
combine(it2->second.id, it2->second.childs[j]);
}
}
检查person
//map<int, person>::iterator it3;
//printf("检查person: \n");
//for (it3 = persons.begin(); it3 != persons.end(); it3++) {
// printf("id: %d ma: %d fa: %d chsize: %d se: %d ar: %d\n", it3->second.id, it3->second.ma, it3->second.fa, it3->second.childs.size(), it3->second.set, it3->second.area);
//}
检查father
//printf("FATHER: \n");
//for (int i = 0; i < father.size(); i++) {
// if (persons.find(i) != persons.end() && (father[i] == i)) {
// printf("%d:%d ", i, father[i]);
// if (i % 10 == 0)printf("\n");
// }
//}
//结果
for (int i = 0; i < father.size(); i++) {
if (persons.find(i) != persons.end() && (father[i] == i)) {
person tmp;
tmp.id = i;
tmp.num = persons[i].num;
//printf("set: %d\n", persons[i].set);
//printf("area: %d\n", persons[i].area);
//printf("num: %d\n", persons[i].num);
tmp.setAvg = persons[i].set*1.0 / persons[i].num;
tmp.areaAvg = persons[i].area*1.0 / persons[i].num;
result.push_back(tmp);
}
}
//排序
sort(result.begin(), result.end(), cmp);
//输出
printf("%d\n", result.size());
for (int i = 0; i < result.size(); i++) {
printf("%04d %d %.3f %.3f\n", result[i].id, result[i].num, result[i].setAvg, result[i].areaAvg);
}
return 0;
}
自己写了一版又臭又长的代码
//看到题目中有: 4-digit的时候, 小心输出
//并查集如果有父亲或者孩子, 但没有属性, 记得空的节点也是要插入的
//涉及double的题目, 要小心变量声明, 输入, 输出
//map插入节点的时候要小心, 尤其是这种 父亲, 孩子, 一大堆的
#include<iostream>
#include<vector>
#include<unordered_map>
#include<algorithm>
using namespace std;
struct node {
int id, root;
double set, area;
node() {
root = 0;
area = 0;
set = 0;
}
node(int x) {
id = x;
root = 0;
area = 0;
set = 0;
}
node(int x, double y, double z) {
id = x;
set = y;
area = z;
root = 0;
}
};
int n;
vector<int>father(10010);
unordered_map<int, node>inputs;
vector<int>roots;
vector<node>results;
int find(int x) {
int son = father[x];
while (father[x] != x)x = father[x];
while (son != x) {
int tmp = father[son];
father[son] = x;
son = tmp;
}
return x;
}
void combine(int a, int b) {
int fa1 = find(a);
int fa2 = find(b);
if (fa1 < fa2)father[fa2] = fa1;
else father[fa1] = fa2;
}
//area降序
//id升序
bool cmp(node a, node b) {
if (a.area != b.area)return a.area > b.area;
else return a.id < b.id;
}
int main() {
scanf("%d", &n);
for (int i = 0; i < father.size(); i++)father[i] = i;
for (int i = 0; i < n; i++) {
int tmpId, tmpFa, tmpMa, tmpNum;
double tmpSet, tmpArea;
scanf("%d %d %d %d", &tmpId, &tmpFa, &tmpMa, &tmpNum);
if (tmpFa != -1) {
node tmpNodeFa(tmpFa);
combine(tmpId, tmpFa);
if(inputs.find(tmpFa)==inputs.end()||(inputs[tmpFa].area==0))inputs[tmpFa] = tmpNodeFa;
}
if (tmpMa != -1) {
node tmpNodeMa(tmpMa);
combine(tmpId, tmpMa);
if(inputs.find(tmpMa)==inputs.end() || (inputs[tmpMa].area == 0))inputs[tmpMa] = tmpNodeMa;
}
for (int j = 0; j < tmpNum; j++) {
int tmp;
scanf("%d", &tmp);
node tmpNodeCh(tmp);
if(inputs.find(tmp)==inputs.end() || (inputs[tmp].area == 0))inputs[tmp] = tmpNodeCh;
combine(tmpId, tmp);
}
scanf("%lf %lf", &tmpSet, &tmpArea);
//cout <<"%%%%%%%%%%%" <<tmpSet <<" "<< tmpArea << endl;
node tmpNode(tmpId, tmpSet, tmpArea);
if(inputs.find(tmpId)==inputs.end() || (inputs[tmpId].area == 0))inputs[tmpId] = tmpNode;
//cout << "%%%%" << inputs[tmpId].id << " " << inputs[tmpId].set<<"^^^"<<inputs[tmpId].area << endl;
}
检查输入
//unordered_map<int, node>::iterator it2;
//for (it2 = inputs.begin(); it2 != inputs.end(); it2++) {
// cout << it2->first << ":$$$ " << it2->second.set << ": " << it2->second.area << ": " << it2->second.root << ": " << endl;
//}
unordered_map<int, node>::iterator it;
for (it = inputs.begin(); it != inputs.end(); it++) {
//为自己的根节点计数
inputs[find(it->first)].root++;
//将需要的加和入根节点
if (inputs[find(it->first)].id != it->first) {
inputs[find(it->first)].set += it->second.set;
inputs[find(it->first)].area += it->second.area;
}
}
检查输入
//cout << "################################" << endl;
//unordered_map<int, node>::iterator it3;
//for (it3 = inputs.begin(); it3 != inputs.end(); it3++) {
// cout << it3->first << ": " << it3->second.set << ": " << it3->second.area << ": " << it3->second.root << ": " << endl;
//}
//cout << "################################" << endl;
unordered_map<int, node>::iterator it1;
for (it1 = inputs.begin(); it1 != inputs.end(); it1++)
if (it1->second.root != 0) {
//cout << it1->first << ": " << it1->second.set << ": " << it1->second.area << ": " << it1->second.root << "####" << endl;
it1->second.area /= it1->second.root;
it1->second.set /= it1->second.root;
results.push_back(it1->second);
}
sort(results.begin(), results.end(), cmp);
//cout << "################################" << endl;
//输出的时候记得%04d
printf("%d\n", results.size());
for (int i = 0; i < results.size(); i++) {
printf("%04d %d %.3lf %.3lf\n", results[i].id, results[i].root, results[i].set, results[i].area);
}
return 0;
}
并查集+ 联通分量个数+ 节点个数+ 判断两个节点是否在同一个子集
1118
//1.并查集比较绕的点是找谁union, 如果没有要求的话, 找第一个输入的就可以
//2.并查集combine的时候,最好将其他节点作为first的子集
//3.并查集father的初始化是默认的一步
//4.并查集如何记录有多少个节点:输入节点可以用set或map记录
//5.并查集如何判断有几个子集:看father[i]==i的个数
//6.并查集如何判断两个点是否在同一个分支中:find(a)==find(b)
//7.并查集的组成部分:
// (1)初始化
// (2)确定first
// (3)combine
// (4)find
//并查集:
// 1.数据结构:
// (1)vector<int>father; //一开始就 father[i]=i; 初始化
//
// 2.函数:
// (1)int find(int x){
//
// int son=father[x];
// while(father[x]!=x)x=father[x];
//
// while(son!=x){ //路径压缩
// int tmp=father[son];
// father[son]=x;
// son=tmp;
// }
//
// return x; //找到父节点很容易
// }
//
// (2)void combine(int a, int b){
// int fa1=find(a);
// int fa2=find(b);
//
// if(fa1!=fa2)father[fa2]=fa1;
//
// }
//
// 3.操作:
// (1)初始化: father[i]=i
// (2)插入节点: combine(a,b); //可以找一个节点作为参照
// (3)连通分支个数:
// 方法一:for(father)if(father[i]==i&&(sets.find()!=sets.end()))
// 方法二:
// vector<int>myHash;
// for(所有节点)myHash[find(i)]++;
// if(myHash[i]!=0)roots.push_back();
// (4)判断是否同一个连通分支: find(a)==find(b)
#include <stdio.h>
#include <vector>
#include <set>
using namespace std;
vector<int>father(10001);
set<int>birds;
vector<int>results;
int find(int x) {
int son = father[x];
while (father[x] != x)x = father[x];
while (son != x) {
int tmp = father[son];
father[son] = x;
son = tmp;
}
return x;
}
void combine(int a, int b) {
int fa = find(a);
int fb = find(b);
if (fa != fb) father[fb] = fa;
}
int main() {
int n;
scanf("%d", &n); //一进来输入n是常态了
for (int i = 0; i < father.size(); i++)father[i] = i; //并查集的father先初始化也是常态
for (int i = 0; i < n; i++) {
int k, first, bird;
scanf("%d %d", &k, &first); //写错一个逗号, 检查半天
birds.insert(first);
for (int j = 1; j < k;j++) {
scanf("%d", &bird);
birds.insert(bird);
combine(first, bird);
}
}
for (int i = 0; i < father.size(); i++) {
if (father[i] == i&&(birds.find(i)!=birds.end()))results.push_back(i);
}
printf("%d %d\n", results.size(), birds.size());
int k, a, b;
scanf("%d", &k);
for (int i = 0; i < k; i++) {
scanf("%d %d", &a, &b);
if (find(a) == find(b))printf("Yes\n");
else printf("No\n");
}
return 0;
}
//测试时的代码:
//核心: 并查集:连通分支个数 + 并查集:父节点
#include<iostream>
#include<vector>
#include<set>
using namespace std;
int n, queries;
int birdNum, treeNum;
vector<int>father(10010);
set<int>birds;
int find(int x) {
int son = father[x];
while (father[x] != x)x = father[x];
while(son != x) {
int tmp = father[son];
father[son] = x;
son = tmp;
}
return x;
}
void combine(int a, int b) { //?结合的方式不同会不会导致根节点不同
int fa1 = find(a);
int fa2 = find(b);
if (fa1 != fa2)father[fa2] = fa1;
}
//节点个数
//根个数
int main() {
scanf("%d", &n);
for (int i = 0; i < father.size(); i++)father[i] = i;
for (int i = 0; i < n; i++) {
int num, tmp1, tmp2, max = -1;
scanf("%d", &num);
scanf("%d", &tmp1);
birds.insert(tmp1);
for (int j = 1; j < num; j++) {
scanf("%d", &tmp2);
birds.insert(tmp2);
combine(tmp1, tmp2);
}
}
//鸟的个数
birdNum = birds.size();
vector<int>roots(birdNum+1,0);
for (int i = 1; i <= birdNum; i++)
roots[find(i)]++;
//连通分支个数
for (int i = 1; i <= birdNum; i++)
if (roots[i] != 0)treeNum++;
printf("%d %d\n", treeNum, birdNum);
//判断是否同一个连通分支
scanf("%d", &queries);
for (int i = 0; i < queries; i++) {
int a, b;
scanf("%d %d", &a, &b);
int faA = find(a);
int faB = find(b);
if (faA == faB)printf("Yes\n");
else printf("No\n");
}
return 0;
}