带权并查集
例题一链接:
http://acm.hdu.edu.cn/showproblem.php?pid=3038
题意:
给出一些区间和,如果某个区间和与前面的区间和产生矛盾,那么就忽略它
计算错误的区间和的数量
分析:
将每个前缀和看作是一个节点,建立并查集,每个节点不但保存父节点,还保存它与父节点的差值
对于同一个并查集中的节点,他们的差值是确定的,这个时候错误的区间和就能立马发现
不同并查集的节点,他们差值不确定,这个时候可以根据区间和来合并两个并查集
AC代码:
#include<bits/stdc++.h> #define ll long long #define pii pair<int,int> using namespace std; const int maxn=2e5+7; const int mod=1e9+7; ll offe[maxn],boss[maxn]; int fin(int x){ if(x==boss[x])return x; int z=boss[x]; int y=fin(boss[x]); offe[x]+=offe[z]; boss[x]=y; return y; } int main() { int n,m,ans=0; while(scanf("%d %d",&n,&m)==2){ ans=0; for(int i=0;i<=n;i++)boss[i]=i,offe[i]=0; for(int i=1;i<=m;i++){ int l,r,x; scanf("%d %d %d",&l,&r,&x); l--; if(fin(l)==fin(r)){ if(offe[l]-offe[r]!=x)ans++; }else{ int fl=fin(l),fr=fin(r); boss[fl]=fr; offe[fl]=x-offe[l]+offe[r]; } } printf("%d\n",ans); } return 0; }
例题二链接:
https://codeforces.com/problemset/problem/1290/C
题意:
给出一排灯泡的状态,和一些操作集合,每个集合包括一些灯泡,代表改变这些灯泡的状态
每个灯泡最多保存在两个集合中,求使得每个前缀灯泡都点亮的最少操作数
分析:
为操作建立两个节点,一个是使用这个操作需要的花费,一个是不使用这个操作的花费
有两种情况,如果某个灯只能被一个集合包含,那么这个操作集合一定执行或者不执行
如果这个灯被两个集合包含,如果灯亮的,那么两个集合都激活,或者都不激活,如果灯灭的,那么只能有一个集合激活
利用这些关系,将他们连接起来,取最优解,具体看代码
参考:https://www.cnblogs.com/uid001/p/12272628.html
AC代码:
#include<bits/stdc++.h> #define ll long long using namespace std; const int maxn=3e5+7; vector<int>ve[maxn]; int boss[maxn*2],sz[maxn*2],n,k; char S[maxn]; int fin(int x){ if(x==boss[x])return x; return boss[x]=fin(boss[x]); } void unio(int x,int y){ x=fin(x),y=fin(y); if(x!=y){ boss[x]=y; sz[y]+=sz[x]; } } int cal(int x){ return min(sz[fin(x)],sz[fin(x+k)]); } int main(){ scanf("%d %d",&n,&k); scanf("%s",S+1); for(int i=1;i<=2*k+1;i++)boss[i]=i; for(int i=k+1;i<=2*k;i++)sz[i]=1; sz[2*k+1]=1e9; for(int i=1;i<=k;i++){ int num,x; scanf("%d",&num); while(num--){ scanf("%d",&x); ve[x].push_back(i); } } int ans=0; for(int i=1;i<=n;i++){ if(ve[i].size()==1){ ans-=cal(ve[i][0]); if(S[i]=='1')unio(ve[i][0]+k,2*k+1); else unio(ve[i][0],2*k+1); ans+=cal(ve[i][0]); }else if(ve[i].size()==2){ int a=ve[i][0],b=ve[i][1]; if(fin(a)!=fin(b)&&fin(a)!=fin(b+k)){ ans-=(cal(a)+cal(b)); if(S[i]=='1')unio(a,b),unio(a+k,b+k); else unio(a,b+k),unio(a+k,b); ans+=cal(a); } } printf("%d\n",ans); } return 0; }