并查集
感谢cyanigence-oi大佬(Orz)的题单
模板
并查集 模板题
预处理过程:void pre(),赋值数组a
假设自己就是祖先,则用for循环,来储存每个父亲节点的值
void pre() { for(int i=1;i<=n;i++) a[i]=i; }
查询过程:int find(int k)
用递归的形式一层一层的探索自己的祖先,根节点表示的就是祖先,判断两个人是不是同一个祖先,只要查询各自的根节点是否相同即可
int find(int k) { if(a[k]==k) return k; else return a[k]=find(a[k]); }
合并过程:void merge(int u,int v)
void merge(int u,int v) { a[find(u)]=find(v); }
按秩合并
P1551 亲戚
#include <bits/stdc++.h> using namespace std ; typedef long long ll; const int maxn = 2e5 + 10; typedef long long ll; ll n,m,p; int a[maxn]; void pre() //预处理 { for(ll i=1;i<=n;i++) { a[i]=i; } } ll find(ll k) //查询+路径压缩 { if(a[k]==k) return k; else return a[k]=find(a[k]); } void merge1(ll u,ll v) //合并路径 { a[find(u)]=find(v); } int main() { scanf("%lld%lld%lld",&n,&m,&p); pre(); ll x,y; for(ll i=1;i<=m;i++) { scanf("%lld%lld",&x,&y); merge1(x,y); } for(ll i=1;i<=p;i++) { scanf("%lld%lld",&x,&y); if(find(x)==find(y)) printf("Yes\n"); else printf("No\n"); } }
P2814 家谱
用标准库的map
#include <bits/stdc++.h> using namespace std; map<string,string>p; string find(string x) { if(p[x]==x) return x; else return p[x]=find(p[x]); } int main() { string sss,ss; char c; while(1) { scanf("%c",&c); if(c=='$') break; else if(c=='#') { cin>>ss; if(p[ss]=="") p[ss]=ss; } else if(c=='?') { cin>>sss; cout<<sss<<" "<<find(sss)<<endl; } else if(c=='+') { cin>>sss; p[sss]=ss; } } }
P1536 村村通
找查两两村是否联通,即是否为同一个祖先,变量s作为记录连通块中的边数,因为并不是每个村和其他的村庄都是联通的,有可能存在鼓励的村庄。
#include <bits/stdc++.h> using namespace std ; typedef long long ll; const ll maxn=2e5+10;; ll n,m,a[maxn]; inline void pre() { for(ll i=1; i<=n; i++) { a[i]=i; } } inline ll find(ll k) { if(a[k]!=k) a[k]=find(a[k]); return a[k]; } void merge(ll u,ll v) { a[find(u)]=find(v); } int main() { while(1) { ll f; scanf("%lld",&f); n=f; if(f==0) return 0; pre(); scanf("%lld",&m); ll s=0; for(ll i=1; i<=m; i++) { ll x,y; scanf("%lld%lld",&x,&y); merge(x,y); } for(ll i=1; i<=n; i++) { if(a[i]==i) { s++; } } cout<<s-1<<endl; } }
P1396 营救
创建结构体,存入u,v,w,对拥挤度w进行升序排序;
并查集,当查询到s和t连通时,输出当前的最大拥挤度,由于之前已经有过升序排序的操作,这时候的最大拥挤度是最小的。
#include <bits/stdc++.h> using namespace std ; typedef long long ll; const ll maxn=2e5+10; struct node { ll u,v,w; }way[maxn]; inline bool cmp(node a,node b) { return a.w<b.w; } ll n,m,s,t; ll a[maxn]; void pre() { for(ll i=0;i<maxn;i++) { a[i]=i; } } inline ll find(ll k) { if(a[k]!=k) { a[k]=find(a[k]); } return a[k]; } inline void merge(ll uu,ll vv) { ll uuu=find(uu),vvv=find(vv); if(a[uuu]!=vvv) a[uuu]=vvv; } int main() { scanf("%lld%lld%lld%lld",&n,&m,&s,&t); pre(); for(ll i=1;i<=m;i++) { scanf("%lld%lld%lld",&way[i].u,&way[i].v,&way[i].w); } sort(way+1,way+1+m,cmp); for(ll i=1;i<=m;i++) { merge(way[i].u,way[i].v); if(find(a[s])==find(a[t])) { printf("%lld\n",way[i].w); break; } } }
P1621 集合
P4185 [USACO18JAN]MooTube
P1197 [JSOI2008]星球大战
bzoj2054疯狂的馒头
P2294 [HNOI2005]狡猾的商人
P1892 [BOI2003]团伙
P1455 搭配购买
并查集+01背包
#include<bits/stdc++.h> using namespace std; typedef long long ll; ll n, m, w; ll a[100005], c[100005], d[100005],dp[100005]; void init() { for (ll i = 1; i <= 100004; i++) { a[i] = i; } } ll find(ll k) { if (a[k] == k) { return k; } else { return a[k] = find(a[k]); } } void merge(ll u, ll v) { a[find(u)] = find(v); } int main() { init(); scanf("%lld %lld %lld", &n, &m, &w); for (ll i = 1; i <= n; i++) { scanf("%lld %lld", &c[i], &d[i]); } for (ll i = 1; i <= m; i++) { ll x, y; scanf("%lld %lld", &x, &y); merge(x, y); } ll s = 0; for (ll i = 1; i <= n; i++) { if (a[i] != i) { d[find(i)] += d[i]; d[i] = 0; c[find(i)] += c[i]; c[i] = 0; } } for (ll i = 1; i <= n; i++) { for (ll j = w; j >= c[i]; j--) { dp[j] = max(dp[j], dp[j - c[i]] + d[i]); } } printf("%lld\n", dp[w]); }
带权并查集
公司竞争
虽然题目给了2秒,但是也不可以一遍一遍的查询,大佬何哥给了一种思路,即在每次认祖宗的时候,把儿子的势力值加到父亲的势力值中,时间可以大大缩短,给1秒都够
#include<bits/stdc++.h> using namespace std; typedef long long ll; ll n, m, k; ll a[100005], t[100005]; void init() { for (ll i = 0; i < 100005; i++) { a[i] = i; } } ll find(ll k) { if (a[k] == k) { return k; } else { return a[k] = find(a[k]); } } void merge(ll u, ll v) { a[find(u)] = find(v); } int main() { init(); scanf("%lld %lld",&n,&m); for(ll i=1;i<=n;i++) { scanf(" %lld",t+i); } for(ll i=1;i<=m;i++) { ll f,x,y; scanf("%lld",&f); if(f==1) { scanf("%lld %lld",&x,&y); ll xx=find(x); ll yy=find(y); if(xx!=yy) { a[yy]=xx; t[xx]+=t[yy]; } } else if(f==2) { scanf("%lld",&x); printf("%lld\n",t[find(x)]); } } }
Interesting Computer Game 2020牛客暑期多校训练营(第八场)
#include <bits/stdc++.h> #define T int t ;cin >> t;while(t--) using namespace std ; typedef long long ll; const int maxn = 2e5 + 10; ll vis[maxn],a[maxn],b[maxn],c[maxn],pre[maxn]; //vis数组表示的是当前的节点是否被访问过 //pre数组表示的是合并路径 inline ll find(ll x) { return (x==pre[x]) ? x:pre[x]=find(pre[x]); } inline void merge(ll u,ll v) { ll x=find(u),y=find(v); if(x==y) { vis[x]=1; return; } pre[x]=y;//表示x,y的祖宗合并,即两者为同一祖先 if(vis[x])vis[y]=1;//该节点表示已被访问 } int main() { ll total=0;//用于输出Case情况的个数 T { total++; ll n; ll tot=0;//tot表示存入数的个数 scanf("%lld",&n); for(ll i=1; i<=n; i++) { scanf("%lld%lld",&a[i],&b[i]); c[++tot]=a[i];//c数组用于存放每个数字,方便接下去排序和去重,假设a和b的值完全不一样,那么c数组的大小需要两倍空间,即2e5+5 c[++tot]=b[i]; } for(ll i=0; i<=maxn; i++)//初始化操作 { vis[i]=0; pre[i]=i; } sort(c+1,c+tot+1);//升序排序,便于接下来的去重 int cnt=unique(c+1,c+tot+1)-(c+1);//去重,方便编号 for(ll i=1; i<=n; i++) { a[i]=lower_bound(c+1,c+tot+1,a[i])-c; //从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字, //找到返回该数字的地址,不存在则返回end。 //通过返回的地址减去起始地址begin,得到找到数字在数组中的下标,下标即树的度数 b[i]=lower_bound(c+1,c+tot+1,b[i])-c; merge(a[i],b[i]);//用下标代替实际数字,防止数组存不下 } int ans=tot; for(ll i=1; i<=tot; i++) { if(pre[i]==i&&!vis[i])ans--;//根据《离散数学》相关知识,如果非连通块则ans减一 } cout<<"Case #"<<total<<": "<<ans<<endl; } }
P3958 奶酪
被scy修改过的删边问题
贪心题
只要保证连通,即只要从树根节点到达叶子节点,变成初级通路(每个点只经过一次,边数=点数-1),不必构成回路,结果等于原始总边数(m)-初级通路(n-1)
#include <bits/stdc++.h> using namespace std ; int main() { int a,b; cin>>a>>b; int bb=b; while(b--){int x;cin>>x>>x;} printf("%d\n",bb-a+1); }
家族
查找连通块的个数,如果a[i]==i,则s++,s变量统计的就是连通块的个数。
#include<bits/stdc++.h> typedef long long ll; using namespace std; const ll maxn=1e5+10; ll a[maxn]; void pre() { for(ll i=1;i<maxn;i++) { a[i]=i; } } inline ll find(ll k) { if(a[k]==k) { return a[k]; } else { return a[k]=find(a[k]); } } inline void merge(ll u,ll v) { a[find(u)]=find(v); } ll n,m; int main() { scanf("%lld%lld",&n,&m); pre(); ll x,y; for(ll i=1;i<=m;i++) { scanf("%lld%lld",&x,&y); merge(x,y); } ll s=0; for(ll i=1;i<=n;i++) { if(a[i]==i) s++; } cout<<s<<endl; }
作者:Drophair
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文连接,否则保留追究法律责任的权利。