文章目录
模板
来自于模板
(1) 朴素并查集:
int p[N];//存储每个祖宗节点
//返回x的祖宗节点,且将节点变为祖宗节点的孩子
int find(int x)
{
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
//初始化,假定节点编号是1~n
for(int i=1;i<=n;i++) p[i]=i;
//合并a和b所在的两个集合
p[find(a)]=find(b);
(2)维护size ‘’ 整个集合节点数 ‘’ 的并查集
//p[]存储每个点的祖宗节点,size[]只有祖宗节点有意义,
int p[N],size[N];
//返回x的祖宗节点
int find(int x)
{
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
//初始化,假定节点编号是1~n
for(int i=1;i<=n;i++)
{
p[i]=i;
size[i]=1;
//因为初始的时候每个集合只有一棵树,所以都为 1
}
//合并a和b所在的两个集合
size[find(b)]+=size[find(a)];
p[find(a)]=find(b);
//一定要先维护size,再合并并查集,否则find(b)和find(a)找的是同一个节点,没有意义了
(3)维护到祖宗节点距离的并查集
int p[N],d[N];
//p存储每个点的祖宗节点,d[x]存储x到p[x]的距离
int find(int x)
{
if(p[x]!=x)
{
int u=find(p[x]);
d[x]+=d[p[x]];
//路径压缩,同时累计到祖宗节点的距离
p[x]=u;
}return p[x];
}
for(int i=1;i<=n;i++)
{
p[i]=i;
d[i]=0;
}
//合并a和b所在的两个集合
p[find(a)]=find(b);
d[find(a)]=distance;
//根据具体问题初始化find(a)的偏移量
1249. 亲戚 - AcWing题库
思路
模板题
先初始化P数组,再输入n个关系
用find函数进行路径压缩。
最后判断两者是否有一个共同祖宗。
代码
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
using namespace std;
const int N=20010;
int n,m,q;
int p[N];
int find(int x)//查根
{
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
int main()
{ IOS
cin>>n>>m;
for(int i=1;i<=n;i++)
p[i]=i;//初始化,节点的根是本身
while(m--)
{
int a,b;
cin>>a>>b;
p[find(a)]=find(b);
}
cin>>q;
while(q--)
{int a,b;
cin>>a>>b;
if(find(a)==find(b)) cout<<"Yes\n";
else cout<<"No\n";
}
return 0;
}
237. 程序自动分析 - AcWing题库
给出几个等式和不等式的条件,看这些条件是否同时合理
思路
等号具有传递性,而不等号没有,我们可以将所有等号合并,再判断不等的两个数是否在一个集合里。
由于数据较大,所以采用离散化。
运用模板find等,将相等的合并。
并查集擅长动态维护许多具有传递性的关系,即连通关系
在对P数组进行初始化时,不要越界,
改了几个小时,才发现原来这错了,越界后会将idx变成200001.这和数组的本质是指针有关
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
unordered_map<int,int> mp;
const int N=2e5+10;
int p[N];
int idx=0;
int s(int x)//离散化
{
if(mp.count(x))
return mp[x];
return mp[x]=idx++;
}
int find(int x)//路径压缩
{
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
void solve()
{
int n;
vector<pair<int,int> > v;//清空
mp.clear();//清空
idx=1;
cin>>n;
//for (int i = 1;i<=N;i++) 数组越界!!!
for(int i=1;i<=2*n;i++)
p[i] = i;//初始化
while(n--)
{
int a,b,e;
cin>>a>>b>>e;
a=s(a),b=s(b);
if(e==1) p[find(a)]=find(b);//合并
else v.push_back({a,b});
}
int f=0;
for(auto [a,b]:v)
{
if(find(a)==find(b))
{
f=1;break;
}
}
if(f) cout<<"NO\n";
else cout<<"YES\n";
}
signed main()
{ IOS
int t;
cin>>t;
while(t--)
solve();
}
145. 超市 - AcWing题库
不同物品,给出价值和过期时间,每天只能卖出一个物品,问最大收益
思路
用反悔贪心可以很清晰的做出来,代码中用了重载运算符
不过时间复杂度较高
并查集:
数组f表示,第i天的祖宗是自己,也就是前i天内最后一个空闲时间是第i天,
将物品价值从大到小排序,先给他安排时间,安排好,将 f [ i ] = i − 1 f[i]=i-1 f[i]=i−1,为什么不写 f [ i − 1 ] f[i-1] f[i−1]呢,因为你怎么保证 f [ i − 1 ] f[i-1] f[i−1]就一定就祖宗呢?最终还是需要用find函数向前找。
安排时间之前,先判断他的祖宗是否>0,即有没有时间安排它。
并查集在这里是
查询过期前的可卖日期
再次合并更新该过期时间的物品可用的空闲日期
代码(反悔贪心)
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define fir(i,a,b) for(int i=a;i<=b;i++)
const int N=1e6+10;
int p[10050],d[10050];
struct commodity{
int p,d;
bool operator < (const commodity &t) const{return d<t.d;}
//重载运算符,返回bool类型
}a[N];
signed main()
{
IOS
int n;
while(cin>>n)
{
priority_queue<int> p;
int ans=0;
fir(i,1,n)
cin>>a[i].p>>a[i].d;
sort(a+1,a+n+1);//先排序,优先日期小的
priority_queue<int,vector<int> ,greater<int> > q;//小顶堆,把小的踢出去
fir(i,1,n)
{
ans+=a[i].p,q.push(a[i].p);
if(q.size()>a[i].d)
{
ans-=q.top();
q.pop();
}
}
cout<<ans<<endl;
}
}
代码(并查集)
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define fir(i,a,b) for(int i=a;i<=b;i++)
const int N=1e5+10;
int f[N];
struct commodity{
int p,d;
}a[N];
int find(int x)//模板
{
if(f[x]!=x) f[x]=find(f[x]);
return f[x] ;
}
bool cmp(commodity a,commodity b)
{
if(a.p!=b.p) return a.p<b.p;
return a.d<a.d;
}
signed main()
{
IOS
int n;
while(cin>>n)
{
int ans=0,maxd=0;
fir(i,1,n)
{int h,j;
cin>>h>>j;
a[i].p=-h,a[i].d=j;//为了p从大到小排
maxd=max(j,maxd);
}
for(int i=0;i<=maxd;i++) f[i]=i;
sort(a+1,a+n+1,cmp);//先排序,优先价值大日期小的
fir(i,1,n)
{
int v=-a[i].p,e=a[i].d;
int pos=find(e);
if(pos>0)
{
ans+=v;
f[pos]=pos-1;
}
}
cout<<ans<<endl;
}
}