并查集 1.判断成环否
作者:互联网
注意输入!!
并查集
注意更改合并根节点数量 要先更改数量 然后再合并结点
tle 请看看自己查父函数有没有return
或者使用
int find(int x)//找到x的祖宗节点。
{
if(x!=p[x])
p[x]=find(p[x]);
return p[x];
}//这样写对查询的某个点 进行了转化p[x]=根节点
集合的数量 在根节点上保存 所以合并之后更新根节点
带权并查集 :只有一个树根节点但会变 需要维护书中的距离
判断是否成环 https://www.acwing.com/activity/content/problem/content/1579/
题目给的是一个个 不同的点的时候 问给出点之后能不能成环 成环需要 P[a]==p[b]
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 40010;
int p[N];
int n,m;
int find(int x){
if(p[x]!=x){
p[x]=find(p[x]);
}
return p[x];
}
int get(int a,int b){
return a*n+b;
}
int main()
{
cin >> n>>m;
for (int i = 0; i < n*n; i ++ ){
p[i]=i;
}
string op;
int res=0;
for (int i = 1; i <= m; i ++ ){
int x,y;cin>>x>>y>>op;
x--,y--;
int a=get(x,y);
int b;
if(op=="D"){
b=get(x+1,y);
}else{
b=get(x,y+1);
}
int pa=find(a),pb=find(b);
if(pa==pb){
res=i;
break;
}
p[pa]=pb;
}
if(!res) cout << "draw";
else
cout << res;
return 0;
}
连通块+01背包
int n, m, vol;
int v[N], w[N];
int p[N];
int f[N];
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main()
{
cin >> n >> m >> vol;
for (int i = 1; i <= n; i ++ ) p[i] = i;
for (int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i];
while (m -- )
{
int a, b;
cin >> a >> b;
int pa = find(a), pb = find(b);
if (pa != pb)
{
v[pb] += v[pa];
w[pb] += w[pa];
p[pa] = pb;
}
}
// 01背包
for (int i = 1; i <= n; i ++ )
if (p[i] == i)
for (int j = vol; j >= v[i]; j -- )
f[j] = max(f[j], f[j - v[i]] + w[i]);
cout << f[vol] << endl;
return 0;
}
离散化+并查集 https://www.acwing.com/problem/content/239/
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2e6+10;
int n,m;
int p[N];
unordered_map<int,int>s;
struct que{
int x,y,e;
}query[N];
int find(int x){
if(x!=p[x]){
p[x]=find(p[x]);
}
return p[x];
}
int get(int x){
if(s.count(x)==0) s[x]=++n;
return s[x];
}
int main()
{
int t;cin>>t;
ios::sync_with_stdio(false);
cin.tie(0);
while(t--){
n=0;s.clear();
cin >> m;
for (int i = 0; i < m; i ++ ){
int x,y,e;
cin >> x>>y>>e;
query[i]={get(x),get(y),e};
}
for (int i = 1; i <= n; i ++ ) p[i]=i;
for (int i = 0; i < m; i ++ ){
if(query[i].e==1){
int pa=find(query[i].x),pb=find(query[i].y);
p[pa]=pb;
}
}
bool flag=false;
for (int i = 0; i < m; i ++ ){
if(query[i].e==0){
int pa=find(query[i].x),pb=find(query[i].y);
if(pa==pb){
flag=true;
break;
}
}
}
if(flag) puts("NO");
else puts("YES");
}
return 0;
}
带边权并查集 离散化+带边权
https://www.acwing.com/problem/content/241/
复杂的 麻烦的我吐了 。。。
const int N = 20010;
int n, m;
int p[N], d[N];
unordered_map<int, int> S;
int get(int x)
{
if (S.count(x) == 0) S[x] = ++ n;
return S[x];
}
int find(int x)
{
if (p[x] != x)
{
int root = find(p[x]);//root变成了根节点 因为后面return 是找到根的时候了
d[x] += d[p[x]];//先让距离增加
p[x] = root;//再设置父节点为根节点
}
return p[x];
}
int main()
{
cin >> n >> m;
n = 0;
for (int i = 0; i < N; i ++ ) p[i] = i;
int res = m;
for (int i = 1; i <= m; i ++ )
{
int l, r;
string type;
cin >> l >> r >> type;
a = get(l - 1), b = get(r);
int t = 0;
if (type == "odd") t = 1;//是1类
int pa = find(a), pb = find(b);
if (pa == pb)//在同一个集合里面
{
if (((d[a] + d[b]) % 2 + 2) % 2 != t)//看看是否矛盾
{
res = i - 1;
break;
}
}
else//之前没有这个l-1到r的关系,现在要结合在一起了
{
p[pa] = pb;
d[pa] = d[a] ^ d[b] ^ t;//两类集合之间的那个距离? 若da^?^db=1类 ?=1^da&db
}
}
cout << res << endl;
return 0;
}
网络分析https://www.acwing.com/activity/content/code/content/603227/
题意:一开始的点都为独立的点 完成连接之后的独立的点在连接之前加上的值 单独算
难点 :怎么单独独立算 ( 连接之前让另一个连接节点减去本身的值 那么其下面的节点往上找就不会加错 或 加一个空节点) 不是根节点=d[i]+d[find(i)]
d[x]数组连接的时候怎么处理:细节
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 10010;
int n, m;
int p[N], d[N];
int find(int x)
{
if (p[x] == x || p[find(p[x])] == p[x]) return p[x];//x是根点点 或者 x父节点是根节点 都可以通过返回px表示根节点
int r = find(p[x]);
d[x] += d[p[x]];
p[x] = r;
return r;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ ) p[i] = i;
while (m -- )
{
int t, a, b;
scanf("%d%d%d", &t, &a, &b);
if (t == 1)
{
a = find(a), b = find(b);
if (a != b)//原来不是一个根节点的时候才这么处理
{
d[a] -= d[b];//关键连接之前先减去这个值
p[a] = b;
}
}
else
{
a = find(a);
d[a] += b;
}
}
for (int i = 1; i <= n; i ++ )
if (i == find(i)) printf("%d ", d[i]);//这一步必须要find(i);//如果1->2->3 但是第一步合并2,4 然后又在4上面价值 那么变成1->2->4 3->4 1不是直接和4相连所以需要find一下 d[i]+d[find(i)]是建立在i直接连的根节点基础上
else printf("%d ", d[i] + d[find(i)]);
puts("");
return 0;
}
修改数组https://www.acwing.com/problem/content/1244/
![]
(https://www.icode9.com/i/l/?n=22&i=blog/2525875/202204/2525875-20220405231857775-32125794.png)
p[x]里面的根节点 x指向下一个应该填的数
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2e6;
int p[N];
int find(int x){
if(x!=p[x]) p[x]=find(p[x]);
return p[x];
}
int main()
{
int n;cin>>n;
for (int i = 1; i < N; i ++ ){
p[i]=i;
}
for (int i = 0; i < n; i ++ ){
int x;cin>>x;
x=find(p[x]);
p[x]=x+1;
cout << x<<" ";
}
return 0;
}
题目好朋友
const int n=110;
int father[N];//用于查询,下标是某个节点,值是存放父亲结点
int isRoot[N];//存放每个集合的代表结点
int findfather(int x)//寻找父亲的函数
{ //int a=x;//为了路径压缩
while(x!=father[x]){
x=father[x];
}
//while(a!=father[a]){ //路径压缩
// int z=a;
// a=father[a];
// father[z]=a;
//}
return x
void union(int a,int b){
int faA=findfather(a);//表示根节点
int faB=findfather(b);//表示根结点
if(faA!=faB){
father[faA]=faB;//将a最上面结点的父亲从自己设为b最上面的父亲
}
}
void init(int n){
for(int i=1;i<n;i++){
father[i]=i;
isRoot[i]=false;//该结点的代表
}
}
}
int n,m,a,b;
cin<<n<<m;
init(n);//n表示总共有的人进行初始话
for(int i=0;i<m;i++){//m表示为所在的集合
cin<<a<<b;
union(a,b);
}
for(int i=1;i<=n;i++){//只有跟结点才设为真
isroot[findfather(i)]=true;
}
int ans=0;
for(int i=1;i<=n;i++){//统计
ans+=isroot[i];
}
cout<<ans;
return 0;
计算连通块
int calblock(int n){//传入一个值
int block=0;
for(int i=1;i<=n;i++){
root[findfathher(i)]=true;//让根节点变为1
}
for(int i=1;i<=n;i++){
block+=root[i];//然后加上这个就行了
}
return block;
}
#include <bits/stdc++.h>
using namespace std;
int father[5005];
int isroot[5005];
int findfather(int x){//寻父亲函数
// int a=x;
while(x!=father[x])
x=father[x];
//while(a!=father[a]){
// int z=a;
// a=father[a];
// father[z]=x;
}
void Union(int a,int b){//联合函数
int faA=findfather(a);
int faB=findfather(b);
if(faA!=faB){
father[faA]=faB;
}
}
void init(int n){//初始话函数
for(int i=1;i<=n;i++){
father[i]=i;
isroot[i]=false;
}
}
int main()
{
int n,m,p;
cin>>n;
init(n);//初始话
int a,b;
cin>>m; cin>>p;
for(int i=1;i<=m;i++){
cin>>a>>b;
Union(a,b);//联合
}
for(int i=1;i<=p;i++){//分析每一对
cin>>a>>b;
if(findfather(a)==findfather(b))
cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
return 0;
}
洛谷:修复公路,连成几个的问题,都是用n--,当n==某值的时候输出
#include <bits/stdc++.h>
using namespace std;
int father[5005];
int isroot[5005];
struct way{
int x,y,z;
}a[100005];//设计到结构体的排序问题
int findfather(int x)
{ int a=x;
while(x!=father[x])
x=father[x];
while(a!=father[a]){
int z=a;
a=father[a];
father[z]=x;
}
}
void Union(int x,int y){
int faX=findfather(x);
int faY=findfather(y);
if(faX!=faY)
father[faX]=faY;
}
void init(int n){
for(int i=1;i<=n;i++){
isroot[i]=false;
father[i]=i;
}
}
bool cmp(way a ,way b){
return a.z<b.z;
}
int main()
{
int n,m,time=0;
cin>>n>>m;
init(n);
int z,x,y;
for(int i=1;i<=m;i++){
cin>>a[i].x>>a[i].y>>a[i].z;
}
sort(a ,a+m+1 , cmp);
// for(int i=1;i<=m;i++) cout<<a[i].z<<" ";
for(int i=1;i<=m;i++){
if(findfather(a[i].x)!=findfather(a[i].y)){
Union(a[i].x,a[i].y);//只要不是同一个集合就联合起来
n--;
}
if(n==1) {//发现只剩一个村庄的时候,说明已经成为了一个整体
cout<<a[i].z;//因为修路可以同时进行
return 0;
}
}
cout<<"-1";
return 0;
}
通信系统http://codeup.hustoj.com/problem.php?cid=100000615&pid=0
有小坑:额外附加了一个条件,单个端点只接收一次消息,所以,不能有环出现,
只有不是环的时候才能输出yes
怎么判断?对图进行树化,只能根据树的边数为n-1定则,而且要所有端点必须为同一集合,那么M必须等于N-1
找个数 输出王先生可以保留的最大男孩数量。(即有多少个人,在房间里的人都是朋友在房间里)
#include <cstdio>
#include <vector>
#include <utility>
using namespace std;
const int maxn=10000010;
int father[maxn]={0},num[maxn];
int maxnum;
int findFather(int a)
{
int x=a;
while(x!=father[x])
{
x=father[x];
}
while(a!=father[a])
{
int z=a;
a=father[a];
father[z]=x;
}
return x;
}
void Union(int a, int b)
{
int A=findFather(a);
int B=findFather(b);
if(A!=B)//不是一个集合的话 有变动 注意
{
father[A]=B;//选择将一个根结点的父亲变为另一根结点
num[B]+=num[A];//让另一个根结点的数值增加,
if(maxnum<num[B]) 并且更新最大值
maxnum=num[B];
}(要选出每个集合中个数的最大值)
}
void init(int n)
{
father[n]=n;
num[n]=1;
}
int main()
{
int n,dot1,dot2;
vector<pair<int,int> > input;
while(~scanf("%d",&n))
{
if(n==0)
{
printf("1\n");
continue;
}
input.clear();
for(int i=0;i<n;++i)
{
scanf("%d %d",&dot1,&dot2);//输入每对的信息
init(dot1);//一个个人初始化节省空间?
init(dot2);
input.push_back(make_pair(dot1,dot2));
}
maxnum=0;
for(int i=0;i<input.size();++i)
{
Union(input[i].first,input[i].second);//联合
}
printf("%d\n",maxnum);
}
return 0;
查找图的联通块
#include<bits/stdc++.h>
using namespace std;
const int N=1005;
vector<int>graph[N];
bool vis[N];
int isfather[N];
int isroot[N];
int findfather(int i){
int x=i;
while(x!=isfather[x]){
x=isfather[x];//父节点=等于祖宗结点
}
while(i!=isfather[i]){
int n=i;
i=isfather[i];
isfather[n]=x;
}return x;
}
void combine(int x,int y){
int faa=findfather(x);
int fab=findfather(y);
if(faa!=fab){
isfather[faa]=fab;
}
}
void init(){
for(int i=0;i<N;i++){
isfather[i]=i;
vis[i]=false;
}
}//查父合并初始话
int main(){
int n,m,k;
cin>>n>>m>>k;
for(int i=0;i<m;i++){
int x,y;
cin>>x>>y;
graph[x].push_back(y);
graph[y].push_back(x);
}
int currentpoint;
for(int i=0;i<k;i++){
cin>>currentpoint;
init();
for(int i=0;i<=n;i++){//遍历每个顶点,枚举每条边,合并每个边
for(int j=0;j<graph[i].size();j++){
if(i==currentpoint||graph[i][j]==currentpoint) continue;
combine(i,graph[i][j]);
}
}
int block=0;
for(int i=1;i<=n;i++){//你仍然需要遍历所有的顶点
if(i==currentpoint) continue;
int fa_i=findfather(i);//对于这个根节点如果没有遍历过就说明是另外一个块
if(vis[fa_i]==false){
block++;
vis[fa_i]=true;
}
}
if(block>0)
cout<<block-1 <<endl;
else
cout<<block <<endl;
}
return 0;
}
字符串归类 https://www.acwing.com/problem/content/4307/
字符串 只要由相同的字母就可以归为一类 所有字符串被划分为多少类
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2e5+10;
int p[N],n;
int id[26];
char str[55];
int find(int x){
if(x!=p[x]) p[x]=find(p[x]);
return p[x];
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++) p[i]=i;int res=0;
for (int i = 1; i <= n; i ++ ){
cin>>str;
for (int j = 0; str[j]; j ++ ){
int x=str[j]-'a';
if(id[x]){
p[find(id[x])]=i;//如果这个字母出现过 就把这个字母的之前出现的字符串合并到当前出现的字符串
}
else id[x]=i;
}
}
for(int i=1;i<=n;i++) if(p[i]==i) res++;;
cout << res;
}
带权并查集 食物链
#include <iostream>
using namespace std;
const int N = 50010;
int n, m;
int p[N], d[N];
1 -> 2 -> 3-> 跟
1 -> 2 -> 跟
d[x] d [p [x] ]
1-> 跟
int find(int x)
{
if (p[x] != x)//不是根节点
{
int t = find(p[x]);//存放根节点
d[x] += d[p[x]];//d[px]存放的是到根节点的距离,因为上一步递归把p[x]变成了根节点
p[x] = t;//把
}
return p[x];
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ ) p[i] = i;
int res = 0;
while (m -- )
{
int t, x, y;
scanf("%d%d%d", &t, &x, &y);
if (x > n || y > n) res ++ ;
else
{
int px = find(x), py = find(y);//px表示x的根节点
if (t == 1)
{
if (px == py && (d[x] - d[y]) % 3) res ++ ;//如果现在的两个结点已经连接到一起了 计算他们的距离 如果是同类 那么到3的距离余的距离应该相等位0 这里为1说明不成立是假话
else if (px != py)//如果没有连接到一起而且必是真话 需要连接到一起
{
p[px] = py;//把py便成根节点
d[px] = d[y] - d[x];//如果同类 那么 d[x]+d[p[x]] == d[y] 在模3的意义下相等 所以反推出d[x]
}
}
else//设x能吃y 那么x比y少1 1吃0
{
if (px == py && (d[x] - d[y] - 1) % 3) res ++ ;//如果x吃y 那么x和 y+1在模3 的意义下相等 这里余数不为0 数说明是假话
else if (px != py)//必然是真话
{
p[px] = py;
d[px] = d[y] + 1 - d[x];//如果x吃y d[x]+d[px] == d[y]-1 在3的意义下
}
}
}
}
printf("%d\n", res);
return 0;
}
标签:判断,return,int,查集,father,节点,成环否,include,find 来源: https://www.cnblogs.com/liang302/p/15713015.html