每当我们看到“最大值最小”“路径上的最大最小值”等字眼时,我们就可以考虑并查集。
我们可以尝试把这些问题转化为某种意义上按单调顺序的合并,利用并查集求解答案。以下时两例并查集的巧妙应用。
CF1213G Path Queries
注意“最大权值不大于q”,加上允许离线,我们可以把边按照权值排序,并一条一条加边,即合并两端点所在的连通块。设当前边端点为 u,v,权值为w,因为排过序,所以一条边的端点所在联通块内的边权一定比w小,也就是说,最大权值等于q的简单路径就会有siz【u】*siz【v】条,也就是当前边对答案的贡献。最后用前缀和合并一下,就能得到答案。这道题做法很多,点分治,Kruskal 重构树等均可,但我认为并查集是最好写的。
#include<bits/stdc++.h> using namespace std; #define int long long const int N=5e5+55; int siz[N],fa[N],m,n; struct edge{ int u,v,w; }e[N<<1]; int q[N],ans[N]; int find(int x){ if(x==fa[x]) return x; return fa[x]=find(fa[x]); } bool cmp(edge a,edge b){ return a.w<b.w; } signed main(){ cin>>n>>m; for(int i=1;i<n;i++){ cin>>e[i].u>>e[i].v>>e[i].w; } for(int i=1;i<=n;i++) fa[i]=i,siz[i]=1; sort(e+1,e+n,cmp); for(int i=1;i<=m;i++) cin>>q[i]; //sort(q+1,q+1+m); for(int i=1;i<n;i++){ //ans[e[i].w]=ans[e[i-1].w]; int u=e[i].u,v=e[i].v; int x=find(u),y=find(v); ans[e[i].w]+=siz[x]*siz[y]; fa[y]=x;siz[x]+=siz[y]; } for(int i=1;i<N;i++) ans[i]+=ans[i-1]; for(int i=1;i<=m;i++){ cout<<ans[q[i]]<<" "; } }View Code
CF444E DZY Loves Planting
此题正解是二分答案+网络流,并且出现在我校网络流专题考试中。然而,除了CF官方题解,似乎没有哪个大冤种拿网络流写(除了考场上的我)
注意到题干中的min,求最大值的最小值最大,且离线,和上一题套路一样,把边按照边权从小到大排序,一个一个加。则连通块内边权一定小于当前边,即两个连通块之间的路径最大值就是这个边的权值,我们只需判断是否合法即可。对于当前已经合并的点,我们都需要给他配一个未合并的点,这样 g(x,y)才会 大于等于w,如果都能分配到,则该答案合法。
形式化的,设siz[u]表示u的连通块的大小,sum为所有x[i]之和,val[u]表示u的连通块内x[i]之和,那么,如果 $size[u]\le sum-val[u]$ ,则该答案合法。我们甚至完全不用二分,只需要按边权枚举每一条边即可,在加边,也就是合并区间的时候,更新fa,siz,val的值。
#include<bits/stdc++.h> using namespace std; #define int long long const int N=2e4+6; int n,m,fa[N],siz[N],val[N],sum; //val 表示 该连通块中xi的和 int find(int u){ if(fa[u]==u) return u; return fa[u]=find(fa[u]); } struct edge{ int u,v,w; }e[N<<1]; bool cmp(edge a,edge b){ return a.w<b.w; } signed main(){ cin>>n; for(int i=1;i<n;i++){ cin>>e[i].u>>e[i].v>>e[i].w; } sort(e+1,e+1+n-1,cmp); for(int i=1;i<=n;i++){ fa[i]=i; siz[i]=1; } for(int i=1;i<=n;i++){ cin>>val[i]; sum+=val[i]; } for(int i=1;i<=n-1;i++){ int u=e[i].u,v=e[i].v; int x=find(u),y=find(v); fa[x]=y; siz[y]+=siz[x];val[y]+=val[x]; if(siz[y]>sum-val[y]){ cout<<e[i].w; return 0; } } cout<<e[n-1].w; return 0; }View Code
[HNOI2005]狡猾的商人
此题的标准做法是差分约束,但是用带权并查集也可以。我觉得他们本质是相同的,而显然带权并查集更好写。
对于此题而言,每条信息(l,r,w),将l,r放入一个集合中,并维护c[l]=s[fa]-s[l],c[r]=s[fa]-s[r],若fa[l]==fa[r],则判断 c[l]-c[r] 与 w 是否相等即可。(这里要用路径压缩,fa也就是该集合的根)
#include<bits/stdc++.h> using namespace std; int fa[666],cha[666],T,n,m,x,y,z,p; int s[666]; int find(int x){//带权并查集 if(x==fa[x]) return fa[x]; else{ int t=find(fa[x]); cha[x]+=cha[fa[x]]; fa[x]=t; return fa[x]; } } int main(){ cin>>T; while(T--){ p=1; cin>>n>>m; for(int i=0;i<=n;i++){fa[i]=i;cha[i]=0;} for(int i=1;i<=m;i++){ cin>>x>>y>>z; x--;//qian zhui he if(find(x)!=find(y)){ cha[fa[y]]=cha[x]-cha[y]-z; fa[fa[y]]=fa[x]; }else{ if(cha[x]-cha[y]!=z) p=0; } } if(p) cout<<"true"<<endl; else cout<<"false"<<endl; } return 0; }View Code
标签:cha,val,int,查集,CF1213G,fa,HNOI2005,find From: https://www.cnblogs.com/DongPD/p/17498485.html