并查集的刷题
说实话,这几天被并查集日到生无可恋
ε=(´ο`*)))唉……就类似于你被一个很古老的算法抽了一瓜子之后发现很多题都可以用这个算法解决但是——你不会
先来看第一个例题吧! 简单难度qwq
这个题很显然啊,它的一半就是一个裸的不能再裸的并查集了。就是关于朋友的朋友那个
而这时,我们唯一需要的,就是冷静。因为并查集类型的题目,难点就是在于如何判断关系和如何合并上,所以需要冷静地理清关系。
那么我们可以看:合并自己的两个敌人,无非就是相当于合并两个没有确立朋友关系的陌生人。那么在合并时唯一需要注意的就是如何合并。再看,如何合并呢?我们可以考虑一个已经出现在一组敌人关系里的
嗯就是这样,还有别忘了一开始一定要有
#include<iostream>
#include<cstring>
using namespace std;
int enemy[10001],father[10001];
bool record[10001];
struct rela{
int f,t;
}r[100001];
int find(int a)
{
if(a!=father[a])a=find(father[a]);
return a;
}
void unionn(int a,int b)
{
if(father[a]!=father[b])
father[find(a)]=father[find(b)];
}
int main()
{
int n,m;
char a;
cin>>n>>m;
for(register int i=1;i<=n;i++)
{
father[i]=i;
}
for(register int i=1;i<=m;i++)
{
cin>>a;
cin>>r[i].f>>r[i].t;
if(a=='F')unionn(r[i].f,r[i].t);
if(a=='E')
{
if(enemy[r[i].f]==0)enemy[r[i].f]=r[i].t;
else
unionn(r[i].t,enemy[r[i].f]);
if(enemy[r[i].t]==0)enemy[r[i].t]=r[i].f;
else
unionn(r[i].f,enemy[r[i].t]);
}
}
memset(record,0,sizeof(record));
int sum=0;
for(register int i=1;i<=n;i++)
{
record[find(i)]=1;//笨拙地记录qwq
}
for(register int i=1;i<=n;i++)
{
if(record[i]==1)sum++;//笨拙地查找qwq*2
}
cout<<sum;
}
好的惹,再来看第二个
中等难度
居然有大佬用二分图染色做!
其实这个题,也可以形象地用“加权并查集”来形容。因为可以看出来,它的每一组关系都有一个权值。而我们可以考虑两个不同的贪心——每次把当前最小的一组关系合并,和每次把当前最大的一组关系分开。乍一看上去,好像都是一样的。但其实,第一种贪心是不对的,因为如果我们每次都把最小的一组关系合并,我们就不能确定我们当前合并的几个元素里面是否有一组我们从小到大还未枚举到的、恶劣指数更高的关系,所以错。而如果从大到小分开,那么一定不会有更恶劣的关系在同一个监狱里,所以正确。
那么好了,若我们从大到小枚举,该怎么分开呢?很简单,还是记录,不过记录的是 一定 和每个已经枚举过的 关系中 包含的 人(记为
然后,如果以后再碰到和一组关系中包含
So,贴标程
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
struct rela{
int x,y,z;
}f[100005];
int n,m,a[20005],b[20005],i;
inline bool cmp(rela a,rela b)
{
return a.z>b.z;
}
inline int find(int x)
{
if(a[x]!=x) x=find(a[x]);
return x;
}
inline void ad(int x,int y)
{
a[find(x)]=a[find(y)];
}
int main()
{
cin>>n>>m;
for(i=1;i<=n;i++)a[i]=i;
for(i=1;i<=m;i++)
scanf("%d%d%d",&f[i].x,&f[i].y,&f[i].z);
sort(f+1,f+m+1,cmp);
for(i=1;i<=m+1;i++)
{
if(find(f[i].x)==find(f[i].y))
{
printf("%d",f[i].z);
return 0;
}
else
{
if(!b[f[i].x]) b[f[i].x]=f[i].y;
else {ad(b[f[i].x],f[i].y);}
if(!b[f[i].y]) b[f[i].y]=f[i].x;
else {ad(b[f[i].y],f[i].x);}
}
}
}
哦,对,还有一个小优化,由于最后还要特判
那么看第三个例题吧,其实第三个例题从思维量上好像没有第二个那么大,但是他用并查集用的巧妙啊——其实也不是多巧妙,只不过现代年轻人(比如我)不够灵活,太死板了而已。
看题吧
其实这个题就是让我们利用前两个条件来判断前面的关系是否满足,然后用第三个条件来判断之后得到几组关系是否满足。
还是那句话,先冷静分析问题、剖析关系。
·首先,两个生物是同类的条件是,他们不是食物关系。
·其次,两个生物满足
好像……是废话?没什么用?其实不然,它给我们提供了一种思路:用三个并查集,来记录三个不同的角色:
诶?那岂不是要写三个不同的
// luogu-judger-enable-o2
#include<iostream>
#include<cstdio>
using namespace std;
#define MAXM 400001
struct edge{
int to,next;
}e[MAXM];
int head[MAXM<<1],ans[MAXM],f[MAXM<<1],cnt,edge[MAXM][2],GG[MAXM];
bool broken[MAXM<<1];
inline void add(int u,int v){
cnt++;
e[cnt].to=v;
e[cnt].next=head[u];
head[u]=cnt;
}
int find(int x){
if(x==f[x])return x;
return f[x]=find(f[x]);
}
inline void unionn(int a,int b){
f[find(a)]=f[find(b)];
}
int main(){
int n,m,a,b;
cin>>n>>m;
for(int i=0;i<=n-1;i++){
f[i]=i;
}
for(int i=1;i<=m;i++){
cin>>a>>b;
add(a,b);
add(b,a);
edge[i][0]=a;
edge[i][1]=b;
}
int k;cin>>k;
for(int i=1;i<=k;i++){
cin>>a;
broken[a]=true;
GG[i]=a;
}
for(int i=1;i<=m;i++){
if(find(edge[i][0])!=find(edge[i][1])&&!broken[edge[i][0]]&&!broken[edge[i][1]]){
unionn(edge[i][0],edge[i][1]);
}
}
int tot=0;
for(int i=0;i<n;i++){
if(f[i]==i&&!broken[i])tot++;
}
for(int i=k;i>=1;i--){
ans[i]=tot;
for(int j=head[GG[i]];j;j=e[j].next){
if(find(e[j].to)!=find(GG[i])&&!broken[e[j].to]){
tot--;
unionn(e[j].to,GG[i]);
}
}
tot++;
broken[GG[i]]=0;
}
ans[0]=tot;
for(int i=0;i<=k;i++){
cout<<ans[i]<<endl;
}
}