简单学习了一下带权并查集,参考了这个博客,感觉写的不错:https://blog.csdn.net/yjr3426619/article/details/82315133
和一般的并查集相比,代码的变化主要在路径压缩和合并的时候。因为带上了权值,所以路径压缩的时候要更新权值(一般v[x]表示的都是x与par[x]之间的权值,需要更新成x与x的根之间的权值)。然后合并的时候,找到x和y的根px和py,将px连接向py(或py连向px)的时候,也要算出两者的权值。具体的更新代码和图看上面的博客就好,讲的挺清楚。
下面记录三道题目吧,自己实现了博客里的例题。
hdu 3038:题目链接:https://vjudge.net/problem/HDU-3038
大意就是给你一系列区间和,判断有多少个和之前给定的矛盾(如果是矛盾就跳过)。可以用带权并查集来做:把结点看做区间的端点,两个结点之间的权值看做这个区间内的和。当前给定的两个结点属于同一个集合时,可以求出它们的区间和,然后判断是否和给定的值相等,如果不相等则ans++;若不属于同一个集合,就把它们对应的根合并,并求出两个根之间的权值。基本是模板题,但有一个点要注意:每次读入的时候要把左边端点当做开区间(使得左边端点-1即可)。举例:比如区间[1,4],读入了[1,2]和[1,4]后就可以确定[3,4],但是实际执行的时候会把3,4当做在不同的集合。但是改成(0,2]和(2,4]就不会有这个问题了,此时读入[3,4]相当于求(2,4],2和4属于同一个集合。
代码:
#include<cstdio> #include<cstring> using namespace std; const int maxn=200+10; int par[maxn],v[maxn]; int n,m,xx,x,y,t,i,j; int find(int x){ //带权路径压缩 if (par[x]==x) return x; else{ int p=par[x]; par[x]=find(par[x]); v[x]+=v[p]; return par[x]; } } int main(){ //freopen("hdu3038.txt","r",stdin); while (~scanf("%d%d",&n,&m)){ int ans=0; for (i=0;i<=n;i++) par[i]=i; memset(v,0,sizeof(v)); for (i=1;i<=m;i++){ scanf("%d%d%d",&xx,&y,&t); x=xx-1; // int px=find(x);int py=find(y); if (px==py){ int d=v[x]-v[y]; //*** if (d!=t) ans++; } else{ par[px]=py; v[px]=v[y]-v[x]+t; //*** } } printf("%d\n",ans); } //fclose(stdin); return 0; }
hihocoder1515
题目链接:https://vjudge.net/problem/HihoCoder-1515
水题,设v[x]表示x比x当前的父亲par[x]低多少分就行了(我觉得设低多少分舒服一些......
代码:
#include<cstdio> #include<cstring> using namespace std; const int maxn=100+10; int par[maxn],v[maxn]; //v[x]表示x比par[x]低多少分 int s,n,m,q,t,i,j,x,y; int find(int x){ if (par[x]==x) return x; else{ int p=par[x]; par[x]=find(par[x]); v[x]+=v[p]; return par[x]; } } int main(){ //freopen("hiho1515.txt","r",stdin); scanf("%d%d%d",&n,&m,&q); for (i=1;i<=n;i++) par[i]=i; memset(v,0,sizeof(v)); for (i=1;i<=m;i++){ scanf("%d%d%d",&x,&y,&s);s=-s; int px=find(x);int py=find(y); if (px!=py){ par[px]=py; v[px]=v[y]-v[x]+s; } } for (i=1;i<=q;i++){ scanf("%d%d",&x,&y); int px=find(x);int py=find(y); if (px!=py) printf("-1\n");else printf("%d\n",v[y]-v[x]); } //fclose(stdin); return 0; }
poj1182 食物链
题目链接:https://vjudge.net/problem/POJ-1182
这题应该算是经典题了,同时也有点变化。自己脑补了一会成功弄出来了(主要是看到了博客里一个模3的提示......)
设v[x]=0表示x和par[x]是同类,v[x]=1表示x吃par[x],v[x]=2表示x被par[x]吃。更新的时候代码要做相应的变化。一是路径压缩的时候v[x]=(v[x]+v[p])%3,二是求x和y的权值t,t=(v[x]-v[y]+3)%3,三是合并px和py,求px和py之间的权值的时候,v[px]=(v[y]-v[x]+t+3)%3。
上个代码吧:
#include<cstdio> #include<cstring> using namespace std; const int maxn=500+10; int par[maxn],v[maxn]; //v[x]=0:xºÍpar[x]ͬÀà v[x]=1:x³Ôpar[x] v[x]=2:x±»par[x}³Ô int t,i,j,k,n,m,d,x,y,ans; int find(int x){ if (par[x]==x) return x; else{ int p=par[x]; par[x]=find(par[x]); v[x]=(v[p]+v[x])%3; //* return par[x]; } } int main(){ //freopen("poj1182.txt","r",stdin); scanf("%d%d",&n,&k); for (i=1;i<=n;i++) par[i]=i; memset(v,0,sizeof(v)); ans=0; for (i=1;i<=k;i++){ scanf("%d%d%d",&d,&x,&y); if (x<1||x>n||y<1||y>n) { ans++;continue; } int px=find(x);int py=find(y); if (px==py){ t=(v[x]-v[y]+3)%3; //* if (d==1&&t!=0) ans++; if (d==2&&t!=1) ans++; } else{ t=d-1; //* par[px]=py; v[px]=(v[y]-v[x]+t+3)%3; //* } } printf("%d\n",ans); //fclose(stdin); return 0; }