并查集总结

并查集总结

维护连通块的数量的话  可以在修改f[t1]=t2的时候 让sum[t2]+=sum[t1] 即可

在维护每个点到根节点的距离的时候 需要在并查集的过程中维护   即  t=f][x] d[x]+=d[t]

 

题目链接:https://www.acwing.com/problem/content/1252/

转换题意 判断什么时候出现环, 当find1(a)==find1(b) 已经存在的时候,再连上一条边就一定出现环

然后就是二维坐标转换成一维坐标 即  p=y+(x-1)*n

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn=4e4+10;
 4 const int mod=998244353;
 5 #define ll long long
 6 int f[maxn];
 7 int find1(int x)
 8 {
 9     if(x==f[x]) return x;
10     return f[x]=find1(f[x]);
11 }
12 
13 void add(int x,int y)
14 {
15     int t1=find1(x),t2=find1(y);
16     f[t2]=t1;
17 }
18 
19 int main()
20 {
21     ios::sync_with_stdio(0);
22     cin.tie(0);
23     int n,m;
24     cin>>n>>m;
25     for(int i=1;i<=n*n;i++)
26         f[i]=i;
27     int ans=0;
28     for(int i=1;i<=m;i++)
29     {
30         int x,y;
31         char d;
32         cin>>x>>y>>d;
33         if(ans) continue;
34         int u=y+(x-1)*n;
35         int v=u;
36         if(d=='R') v++;
37         else v+=n;
38         if(find1(u)==find1(v))
39             ans=i;
40         add(u,v);
41     }
42     if(!ans) cout<<"draw"<<'\n';
43     else cout<<ans<<'\n';
44 
45 
46 
47 
48 
49 }
View Code

 

题目链接:https://www.acwing.com/problem/content/1254/

弄成一个个连通块 然后 跑01背包问题即可

如何弄成一个个连通块并且将w和v累加起来呢? 只需要在add里面的时候每次 将子节点的值累加到父节点即可

最后每个根节点代表一个连通块

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn=1e4+10;
 4 const int mod=998244353;
 5 #define ll long long
 6 #define pi pair<int,int>
 7 #define fi first
 8 #define sc second
 9 #define pb push_back
10 int f[maxn];
11 int dp[maxn],w[maxn],v[maxn];
12 
13 int find1(int x)
14 {
15     if(x==f[x]) return x;
16     return f[x]=find1(f[x]);
17 }
18 
19 void add(int x,int y)
20 {
21     int t1=find1(x),t2=find1(y);
22     if(t1==t2) return;
23     f[t1]=t2;
24     w[t2]+=w[t1];
25     v[t2]+=v[t1];
26 }
27 
28 
29 int main()
30 {
31     ios::sync_with_stdio(0);
32     cin.tie(0);
33     int n,m,V;
34     cin>>n>>m>>V;
35     for(int i=1;i<=n;i++) f[i]=i;
36     for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
37     while(m--)
38     {
39         int u,v;
40         cin>>u>>v;
41         add(u,v);
42     }
43     for(int i=1;i<=n;i++)
44         if(i==find1(i))
45             for(int j=V;j>=v[i];j--)
46                 dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
47     cout<<dp[V]<<'\n';
48 
49 
50 
51 }
View Code

 

题目链接:https://www.acwing.com/problem/content/240/   ###K

用d[]数组来维护 节点到根节点的距离  可以知道每次 两段互相接上的时候 d[t1]=sum[t2] 即偏移量增加这么多

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn=3e4+10;
 4 const int mod=1e9+7;
 5 #define ll long long
 6 #define pi pair<int,int>
 7 #define fi first
 8 #define sc second
 9 #define pb push_back
10 int f[maxn];
11 int d[maxn];
12 int sum[maxn];
13 
14 int find1(int x)
15 {
16     if(x==f[x]) return f[x];
17     else
18     {
19         int t=f[x];
20         f[x]=find1(f[x]);
21         d[x]+=d[t];
22     }
23     return f[x];
24 }
25 
26 
27 int main()
28 {
29     ios::sync_with_stdio(0);
30     cin.tie(0);
31     int t;
32     cin>>t;
33     for(int i=1;i<maxn;i++)
34         f[i]=i,sum[i]=1;
35     while(t--)
36     {
37         char a;
38         int b,c;
39         cin>>a>>b>>c;
40         if(a=='M')
41         {
42             int t1=find1(b),t2=find1(c);
43             f[t1]=t2;
44             if(t1!=t2)
45             d[t1]=sum[t2];
46             sum[t2]+=sum[t1];
47         }
48         else
49         {
50             int t1=find1(b),t2=find1(c);
51             if(t1!=t2)
52                 cout<<-1<<'\n';
53             else
54                 cout<<abs(d[b]-d[c])-1<<'\n';
55         }
56     }
57 
58 
59 
60 
61 
62 }
View Code

 

 

传送门:https://www.cnblogs.com/winfor/p/14115769.html  四道题  都是带权并查集 即d[x]+=d[t] 这种

 

题目链接:https://www.acwing.com/problem/content/241/         ###K 

思路:首先用前缀和的思想把题目转换成  给定l和r   等价于 l-1 和r 是否是同一类,奇数代表不同一类,偶数代表同一类

然后每次判断的时候,先判断着两个数是否已经有关系(即是否在一个连通块中) 如果有关系的时候根据当前给定判断是否矛盾

否则的话  就带权并查集并在一起, 关于 新建的边权是多少参考下图

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn=2e4+10;
 4 const int mod=1e9+7;
 5 #define ll long long
 6 #define pi pair<int,int>
 7 #define fi first
 8 #define sc second
 9 #define pb push_back
10 unordered_map<int,int>mp;
11 int f[maxn],d[maxn];
12 
13 
14 int tot;
15 int find1(int x)
16 {
17     if(x!=f[x])
18     {
19         int t=f[x];
20         f[x]=find1(f[x]);
21         d[x]^=d[t];
22     }
23     return f[x];
24 }
25 int get(int x)
26 {
27     if(!mp[x]) mp[x]=++tot;
28     return mp[x];
29 }
30 
31 
32 
33 int main()
34 {
35     ios::sync_with_stdio(0);
36     cin.tie(0);
37     int n,m;
38     cin>>n>>m;
39     int ans=m;
40     for(int i=1;i<maxn;i++) f[i]=i;
41     for(int i=0;i<m;i++)
42     {
43         int x,y;
44         string s;
45         cin>>x>>y>>s;
46         if(ans!=m) continue;
47         x=get(x-1),y=get(y);;
48         int t1=find1(x),t2=find1(y);
49         if(s[0]=='e')
50         {
51             if(t1==t2)
52             {
53                 if(d[x]^d[y])
54                     ans=i;
55             }
56             else
57             {
58                 f[t1]=t2;
59                 d[t1]=d[x]^d[y];
60             }
61         }
62         else
63         {
64             if(t1==t2)
65             {
66                 if(!(d[x]^d[y]))
67                     ans=i;
68             }
69             else
70             {
71                 f[t1]=t2;
72                 d[t1]=d[x]^d[y]^1;
73             }
74         }
75     }
76     cout<<ans<<'\n';
77 
78 
79 
80 
81 
82 
83 }
View Code

这次用了无序的离散化, 并且unorder_map 比map更快  可以达到o(1) 不过cf上会被卡成o(n)

用map的好处是在线离散化好写代码, 如果用二分的那个离散化是离线的,需要全部存下来再离散化再处理

而且这里的异或都要写异或,如果用加法的话取得取模防止溢出

如果是根节点的话 d[p]=0  所以每次就算不断查询 也不会修改到当前d[x]的值  d[x]^0=d[x]

// 也可以用扩展域并查集来写

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn=4e4+10;
 4 const int mod=1e9+7;
 5 #define ll long long
 6 #define pi pair<int,int>
 7 #define fi first
 8 #define sc second
 9 #define pb push_back
10 unordered_map<int,int>mp;
11 int f[maxn],d[maxn];
12 
13 
14 int tot;
15 int find1(int x)
16 {
17     if(x==f[x]) return x;
18     return f[x]=find1(f[x]);
19 }
20 void add(int x,int y)
21 {
22     int t1=find1(x),t2=find1(y);
23     f[t1]=t2;
24 }
25 int get(int x)
26 {
27     if(!mp[x]) mp[x]=++tot;
28     return mp[x];
29 }
30 
31 
32 
33 int main()
34 {
35     ios::sync_with_stdio(0);
36     cin.tie(0);
37     int n,m;
38     cin>>n>>m;
39     int ans=m;
40     for(int i=1;i<maxn;i++) f[i]=i;
41     for(int i=0;i<m;i++)
42     {
43         int x,y;
44         string s;
45         cin>>x>>y>>s;
46         if(ans!=m) continue;
47         x=get(x-1),y=get(y);;
48         if(s[0]=='e')
49         {
50             if(find1(x+maxn/2)==find1(y))
51             {
52                 ans=i;
53             }
54             add(x,y);
55             add(x+maxn/2,y+maxn/2);
56         }
57         else
58         {
59             if(find1(x)==find1(y))
60             {
61                 ans=i;
62             }
63             add(x+maxn/2,y);
64             add(x,y+maxn/2);
65         }
66     }
67     cout<<ans<<'\n';
68 
69 
70 
71 
72 
73 
74 }
View Code

主要思路是 x代表x是奇数这个条件,y代表y是奇数这个条件  x+n 和y+n 代表x是偶数,y是偶数的条件

那么每次读取条件的时候,我们只需要把 这些x x+n y y+n 按照实际意义合并在同一个集合中,到时候再判断是否矛盾即可

注意 加边的时候我们加的是对称边,所以判断条件的时候,只需要判断一次即可 比如 find(x)==find(y)  因为如果这个条件成立

那么之前添加条件的时候肯定也添加了 find(x+n)==find(y+n)

 

// 对于处理相对关系的 用可扩展域并查集更好理解, 只有在处理权值的时候才使用带权并查集

// 注意 用可扩展域并查集的话 判断矛盾的时候只需要判断一对关系,因为添加关系的时候添加了对称的关系

 

题目链接:https://www.acwing.com/problem/content/242/   ###K

这种矛盾的问题 用扩展域并查集来写会更加清楚好写

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn=5e4+10;
 4 const int mod=1e9+7;
 5 #define ll long long
 6 #define pi pair<int,int>
 7 #define fi first
 8 #define sc second
 9 #define pb push_back
10 int f[maxn*3];
11 
12 int find1(int x)
13 {
14     if(x==f[x]) return x;
15     return f[x]=find1(f[x]);
16 }
17 
18 void add(int x,int y)
19 {
20     int t1=find1(x),t2=find1(y);
21     f[t1]=t2;
22 }
23 
24 
25 int main()
26 {
27     ios::sync_with_stdio(0);
28     cin.tie(0);
29     int n,k;
30     cin>>n>>k;
31     for(int i=1;i<=n*3;i++) f[i]=i;
32     int ans=0;
33     while(k--)
34     {
35         int d,x,y;
36         cin>>d>>x>>y;
37         if(x>n||y>n)
38         {
39             ans++;
40             continue;
41         }
42         if(d==1)
43         {
44             if(find1(x+n)==find1(y)||find1(x+n+n)==find1(y))
45                 ans++;
46             else
47             {
48                 add(x,y);
49                 add(x+n,y+n);
50                 add(x+n+n,y+n+n);
51             }
52         }
53         else
54         {
55             if(x==y||find1(x)==find1(y)||find1(x+n+n)==find1(y))
56                 ans++;
57             else
58             {
59                 add(x+n,y);
60                 add(x,y+n+n);
61                 add(x+n+n,y+n);
62             }
63         }
64     }
65     cout<<ans<<'\n';
66 
67 
68 
69 
70 
71 }
View Code

x为同类域 x+n为捕食域 x+n+n为天敌域

那么当第一类问题的时候  如果x捕食y 或者x的天敌是y  那么就矛盾了

否则的话 将x的三类域都添加上边

当第二类问题的时候 如果x和y同类那么矛盾,如果x的天敌是y 那么也矛盾

否则的话 将x的三类域都添加上边

每次只需要判完x的部分就行了 因为y跟x对称

 

posted @ 2021-02-01 10:57  canwinfor  阅读(10)  评论(0)    收藏  举报