并查集题选

PTA 家庭房产

给定每个人的家庭成员和其自己名下的房产,请你统计出每个家庭的人口数、人均房产面积及房产套数。

输入格式:

输入第一行给出一个正整数N(≤1000),随后N行,每行按下列格式给出一个人的房产:

编号 父 母 k 孩子1 ... 孩子k 房产套数 总面积

其中编号是每个人独有的一个4位数的编号;分别是该编号对应的这个人的父母的编号(如果已经过世,则显示-1);k(0≤k≤5)是该人的子女的个数;孩子i是其子女的编号。

输出格式:

首先在第一行输出家庭个数(所有有亲属关系的人都属于同一个家庭)。随后按下列格式输出每个家庭的信息:

家庭成员的最小编号 家庭人口数 人均房产套数 人均房产面积

其中人均值要求保留小数点后3位。家庭信息首先按人均面积降序输出,若有并列,则按成员编号的升序输出。

输入样例:

10
6666 5551 5552 1 7777 1 100
1234 5678 9012 1 0002 2 300
8888 -1 -1 0 1 1000
2468 0001 0004 1 2222 1 500
7777 6666 -1 0 2 300
3721 -1 -1 1 2333 2 150
9012 -1 -1 3 1236 1235 1234 1 100
1235 5678 9012 0 1 50
2222 1236 2468 2 6661 6662 1 300
2333 -1 3721 3 6661 6662 6663 1 100

输出样例:

3
8888 1 1.000 1000.000
0001 15 0.600 100.000
5551 4 0.750 100.000
#include<bits/stdc++.h>
using namespace std;

const int N=10001;

struct families
{
	int id,num;
	double total,area;
}family[N];

vector<int>vec;
int n,f[N],cnt[N],sum[N],vis[N],vis1[N];
int find(int x)
{
	if(f[x]==x) return x;
	else return f[x]=find(f[x]);
}

void join(int x,int y)
{
	int fx=find(x),fy=find(y);
	if(fx<fy) f[fy]=fx;
	else if(fx>fy) f[fx]=fy;
}
	
bool cmp(int x,int y)
{
	if(family[x].area!=family[y].area)
	return family[x].area>family[y].area;
	else return family[x].id<family[y].id;
}

int main()
{
	cin>>n;
	for(int i=0;i<N;i++)
		f[i]=i;
	for(int i=1;i<=n;i++)
	{
		int id,fa,ma;
		scanf("%d%d%d",&id,&fa,&ma);
		vis[id]=1;
		if(fa!=-1) join(id,fa),vis[fa]=1;
		if(ma!=-1) join(id,ma),vis[ma]=1;
		int k;
		cin>>k;
		while(k--)
		{
			int child;
			scanf("%d",&child);
			join(id,child);
			vis[child]=1;
		}
		scanf("%d%d",&cnt[id],&sum[id]);
	}
	for(int i=0;i<N;i++)
	{
		if(vis[i])
		{
			int home=find(i);
			family[home].num++;//家族人数加一 
			family[home].total+=cnt[i];
			family[home].area+=sum[i];
			family[home].id=home;
			if(!vis1[home])
			{
				vec.push_back(home);
				vis1[home]=1;
			}
		}
	}
	int cap=vec.size();
	cout<<cap<<endl;
	for(int i=0;i<cap;i++)
	{
		family[vec[i]].total/=family[vec[i]].num;
		family[vec[i]].area/=family[vec[i]].num;
	}
	sort(vec.begin(),vec.end(),cmp);
	for(int i=0;i<cap;i++)
	{
		printf("%04d %d %.3lf %.3lf\n",family[vec[i]].id,family[vec[i]].num,family[vec[i]].total,family[vec[i]].area);
	}
	return 0;
}


PTA 疫情防控

分数 30

疫情尚未结束,严防疫情反复。为了做好疫情防控工作,国内设置了地区风险等级,对于中高风险地区的人员采取限制移动、居家隔离等手段。

为了研究疫情防控对于跨地区交通运输的影响,假设现在有 N 个机场,M 条航线,每天都会新增一个防控地区,一个防控地区会导致一个机场无法正常运作,航线也自然无法正常运行,每天会有 Qi​ 对旅客从 Xi​ 机场前往 Yi​ 机场,请计算有多少对旅客会受到影响无法完成行程。

旅客只要能直达或通过若干次中转,且乘坐的所有航线的出发和到达机场都正常运作,即视作可完成行程。

输入格式:

输入第一行是三个整数 N,M,D (1≤N≤5×104, 1≤M≤2×105, 1≤D≤103), 表示机场数、航线数以及新增防控地区的天数。

接下来首先有 M 行,每行给出空格分隔的两个数字 A 和 B,表示编号为 A 和 B 的机场之间有一条航线。航线是双向的,机场编号从 1 到 N。

然后是 D 块输入,每块输入内第一行为空格分隔的两个整数 C 和 Q (1≤Q≤103),表示新增机场编号为 C 所在的城市为防控地区,今天有 Q 段行程。数据保证新增的城市之前一定不是防控地区。

接下来的 Q 行,每行是空格分隔的两个数字 X 和 Y,表示编号为 X 和 Y 的机场的一段行程。行程有可能包括之前就已经成为防控地区的城市。

输出格式:

对于每天的询问,请在一行中输出在新增了一个防控地区后当天的行程有多少不能成行。

输入样例:

5 5 3
1 2
1 3
1 5
2 5
3 4
4 3
1 3
1 4
2 3
5 3
3 4
2 3
3 5
1 3
2 3
2 5
3 4

输出样例:

1
2
3

时间限制

1000 ms

内存限制

64 MB

查询两个点之间是否有路径,可以使用并查集。

但是并查集并不支持删点操作,如果一定要的话需要在删点后重新建立并查集,这样必然就像我这样超时,最后一个点过不了,得22/30.

#include<bits/stdc++.h>
using namespace std;

const int N=2e5+10;
int n,m,d,fa[N],judge[N];

struct edge
{
	int a,b;
}e[N];

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}

int find(int x)
{
	if(fa[x]==x) return fa[x];
	else return fa[x]=find(fa[x]);
}

void merge(int x,int y)
{
	int fx=find(x),fy=find(y);
	if(fx!=fy) fa[fy]=fx;
}
int main()
{
	cin>>n>>m>>d;
	for(int i=1;i<=m;i++)
	{
		e[i].a=read();
		e[i].b=read();
	}
	while(d--)
	{
		for(int i=1;i<=n;i++) fa[i]=i;
		int x=read(),num=read(),cnt=0;
		for(int i=1;i<=m;i++)
		{
			if(judge[i]||e[i].a==x||e[i].b==x)
			{
				judge[i]=1;
				continue;
			}
			merge(e[i].a,e[i].b);
		}
		//for(int i=1;i<=n;i++)
		//	cout<<find(i)<<' ';
		//cout<<endl;
		while(num--)
		{
			int from=read(),to=read();
			if(find(from)!=find(to)) cnt++;
		}
		cout<<cnt<<endl;
	}
    return 0;
}

可以考虑离线操作,反过来往里面添加边所连的点。

可以先把每个查询都存下来,逆序建并查集就可以过。 

#include<bits/stdc++.h>
using namespace std;

const int N=2e5+10;
int n,m,d,fa[N],judge[N],have[N],cnt[N];
//judge:标记防控地区编号
//have:标记哪些边所连的两点已经进入并查集
//cnt:记录答案
 
struct edge
{
	int a,b;
}e[N];

struct Day
{
	int x,num;
	int from[1010],to[1010];
}day[1001];

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}

int find(int x)
{
	if(fa[x]==x) return fa[x];
	else return fa[x]=find(fa[x]);
}

void merge(int x,int y)
{
	int fx=find(x),fy=find(y);
	if(fx!=fy) fa[fy]=fx;
}

int main()
{
	cin>>n>>m>>d;
	for(int i=1;i<=m;i++)
	{
		e[i].a=read();
		e[i].b=read();
	}
	for(int i=1;i<=n;i++) fa[i]=i;//初始化 
	for(int i=1;i<=d;i++)
	{
		day[i].x=read(),day[i].num=read();
		judge[day[i].x]=1;//标记:在最后一天时 day[i].x是封控区 
		for(int j=1;j<=day[i].num;j++)
		{
			day[i].from[j]=read();
			day[i].to[j]=read();
		}
	}//储存D对应的一块块信息 
	for(int k=d;k>0;k--)//逆序处理 
	{	
		for(int i=1;i<=m;i++)
		{	
			if(have[i]) continue;//此边所连的两点已经进入并查集,无需进行后续操作 
			if(judge[e[i].a]||judge[e[i].b]) continue;//此边的其中一个顶点还是封控区 
			merge(e[i].a,e[i].b);
			have[i]=1;//标记:此边所连的两点已经进入并查集
		}
		judge[day[k].x]=0;//之前的时候 day[k].x还不是封控区 
		//for(int i=1;i<=n;i++)
		//	cout<<find(i)<<' ';
		//cout<<endl;
		for(int i=1;i<=day[k].num;i++)
		{
			int x=day[k].from[i],y=day[k].to[i];
			//cout<<"findx:"<<find(x)<<"findy:"<<find(y)<<endl;
			if(find(x)!=find(y)) cnt[k]++;
		}
	}
	for(int i=1;i<=d;i++)
		cout<<cnt[i]<<endl;
    return 0;
}

P1955 [NOI2015] 程序自动分析https://www.luogu.com.cn/problem/P1955

相等关系是可传递的,不等关系却是不能传递的,一开始没注意调换一下判断的顺序。应该先把判断为是相等关系的放在前面判断,后判断不等关系,看是否会与前面的条件冲突。

#include<bits/stdc++.h>
using namespace std;
int t;
const int N=1000010;
int f[N];
int a[N];

int find(int x)
{
	if(f[x]==x) return f[x];
	else return f[x]=find(f[x]);
}
struct relation
{
	int x,y,e;
}re[N];

bool cmp(relation x,relation y)
{
	return x.e>y.e;
}
int main()
{
	cin>>t;
	while(t--)
	{
		int flag=1,cnt=0;
		int n;
		cin>>n;
		memset(a,0,sizeof(a));
		memset(re,0,sizeof(re));
		for(int i=1;i<=n;i++)
		{
			scanf("%d%d%d",&re[i].x,&re[i].y,&re[i].e);
			a[++cnt]=re[i].x,a[++cnt]=re[i].y;
		}
		sort(a+1,a+cnt+1);
		int total=unique(a+1,a+cnt+1)-(a+1);
		for(int i=1;i<=n;i++)
		{
			re[i].x=lower_bound(a+1,a+total+1,re[i].x)-(a+1);
			re[i].y=lower_bound(a+1,a+total+1,re[i].y)-(a+1);
		}
		sort(re+1,re+n+1,cmp);
		for(int i=1;i<=total;i++) f[i]=i;
		for(int i=1;i<=n;i++)
		{
			if(re[i].e)
			{
				int fx=find(re[i].x),fy=find(re[i].y);
				if(fx!=fy) f[fy]=fx;
			}
			else
			{
				int fx=find(re[i].x),fy=find(re[i].y);
				if(fx==fy)
				{
					flag=0;
					break;
				}
			}
		}
		if(flag) cout<<"YES"<<endl;
		else cout<<"NO"<<endl;
	}
	return 0;
}

UVA1316 Supermarkethttps://www.luogu.com.cn/problem/UVA1316

贪心策略:优先考虑卖出利润大的商品,并对每个商品,在它过期之前尽量卖出,并查集实际上维护了一个数组中“位置”的占用情况。

#include<bits/stdc++.h>
using namespace std;
const int N = 10005;

struct Node 
{
	int val, day;
} product[N];

bool cmp(Node x,Node y)
{
	if(x.day!=y.day) return x.day<y.day;
	else return x.val>y.val;
}

int main()
{
	int n;
	while (cin>>n) 
	{
		memset(product,0,sizeof(product));
		priority_queue<int,vector<int>,greater<int> >q;
		int ans=0;
		for(int i=1;i<=n;i++)
			scanf("%d %d",&product[i].val,&product[i].day);
		sort(product + 1, product + n + 1, cmp); //排序
		for(int i=1;i<=n;i++)
		{
			if(q.size()==product[i].day&&product[i].val>q.top())
			{
				q.pop();
				q.push(product[i].val);
			}
			else if(q.size()<product[i].day)
				q.push(product[i].val);
		}
		while(q.size())
		{
			int temp=q.top();
			q.pop();
			ans+=temp;
		}
		cout<<ans<<endl;
	}
	return 0;
}

P1196 [NOI2002] 银河英雄传说https://www.luogu.com.cn/problem/P1196

边带权的并查集。

找根结点时可以顺便算出到根结点的距离,合并时顺便求出并查集的规模。

#include<bits/stdc++.h>
using namespace std;
const int N=30010;
int f[N],dis[N],size[N],t;

int find(int x)
{
	if(f[x]==x) return f[x];
	else 
	{
		int root=find(f[x]);
		dis[x]+=dis[f[x]];
		return f[x]=root;
	}
}

void merge(int x,int y)
{
	int fx=find(x),fy=find(y);
	f[fx]=fy,dis[fx]=size[fy];
	size[fy]+=size[fx];//规模上:加上新加进来的
}

int main()
{
	for(int i=1;i<=N;i++)
	{
		f[i]=i;
		size[i]=1;//初始时并查集规模为1(只有自己)
        dis[i]=0;//初始时到根结点的距离为0(自己就是根结点)
	}
	cin>>t;
	while(t--)
	{
		//cout<<t<<endl;
		char op;
		int x,y;
		cin>>op>>x>>y;
		if(op=='M')
		{
			merge(x,y);
		}
		if(op=='C')
		{
			int fx=find(x),fy=find(y);
			if(fx==fy)
				cout<<abs(dis[x]-dis[y])-1<<endl;
			else cout<<"-1"<<endl;
		}
	}
}

 P5937 [CEOI1999]Parity Gamehttps://www.luogu.com.cn/problem/P5937

“拓展域” 的并查集。

#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N=5010;
int a[N*2],cnt=0,f1[N],f2[N];

struct Node
{
	int x,y;
	int ans;
}node[N];

int find(int x)
{
	if(f1[x]==x) return f1[x];
	else return f1[x]=find(f1[x]);
}

int merge_1(int x,int y)
{
	int fx1=find(x),fy1=find(y);
	f1[fy1]=fx1;
}

int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		string s;
		scanf("%d%d",&node[i].x,&node[i].y);
		cin>>s;
		if(s=="even") node[i].ans=0;
		else node[i].ans=1;
		a[++cnt]=node[i].x-1;//注意左端点对应的位置
		a[++cnt]=node[i].y;
	}
	sort(a+1,a+cnt+1);
	int total=unique(a+1,a+cnt+1)-(a+1);//离散化
	for(int i=1;i<=total*2;i++)
		f1[i]=i;//1~total对应奇偶性不同,total~2*total对应奇偶性相同
	for(int i=1;i<=m;i++)
	{
		node[i].x=lower_bound(a+1,a+total+1,node[i].x-1)-a;
		node[i].y=lower_bound(a+1,a+total+1,node[i].y)-a;
		if(node[i].ans==0)
		{
			     if(find(node[i].x+total)==find(node[i].y)||find(node[i].x)==find(node[i].y+total))
            //此前已有条件表明左右端点奇偶性不同
			{
				cout<<i-1<<endl;
				return 0;
			}
			merge_1(node[i].x,node[i].y);//合并左端点的奇数域和右端点的奇数域
			merge_1(node[i].x+total,node[i].y+total);//合并左端点的偶数域和右端点的偶数域
		}
		else
		{
			if(find(node[i].x+total)==find(node[i].y+total)||find(node[i].x)==find(node[i].y))
            //此前已有条件表明左右端点奇偶性相同
			{
				cout<<i-1<<endl;
				return 0;
			}
			merge_1(node[i].x+total,node[i].y);//合并左端点的偶数域和右端点的奇数域
			merge_1(node[i].x,node[i].y+total);//合并左端点的奇数域和右端点的偶数域
		}
	}
	cout<<m<<endl;
}

边带权的并查集。没有图还是挺难解释的。我比较懒,就不画图了。 

#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N=5010;
int a[N*2],cnt=0,f1[N],d[N];

struct Node
{
	int x,y;
	int ans;
}node[N];

int find(int x)
{
	if(f1[x]==x) return f1[x];
	int root=find(f1[x]);
	d[x]^=d[f1[x]];
	return f1[x]=root;
}

int merge_1(int x,int y)
{
	int fx1=find(x),fy1=find(y);
	f1[fy1]=fx1;
}

int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		string s;
		scanf("%d%d",&node[i].x,&node[i].y);
		cin>>s;
		if(s=="even") node[i].ans=0;
		else node[i].ans=1;
		a[++cnt]=node[i].x-1;
		a[++cnt]=node[i].y;
	}
	sort(a+1,a+cnt+1);
	int total=unique(a+1,a+cnt+1)-(a+1);
	for(int i=1;i<=total;i++)
		f1[i]=i;
	for(int i=1;i<=m;i++)
	{
		int x=lower_bound(a+1,a+total+1,node[i].x-1)-a;
		int y=lower_bound(a+1,a+total+1,node[i].y)-a;
		int p=find(x),q=find(y);
		if(p==q)
		{
			if(d[x]^d[y]!=node[i].ans)
			{
				cout<<i-1<<endl;
				return 0;
			}
		}
		else
		{
			f1[p]=q;
			d[p]=d[x]^d[y]^node[i].ans;
		}
	}
	cout<<m<<endl;
}

P2024 [NOI2001] 食物链https://www.luogu.com.cn/problem/P2024

搞三个域,一个自身域,一个捕食域,一个被捕食域。

#include<bits/stdc++.h>
using namespace std;
int n,k,cnt;
int f[50010*3];

int find(int x)
{
	if(f[x]==x) return f[x];
	else return f[x]=find(f[x]);
}

int main()
{
	cin>>n>>k;
	for(int i=1;i<=n*3;i++) f[i]=i;//1~n自身域,n~2*n捕食域,2*n~3*n被捕食域
	for(int i=1;i<=k;i++)
	{
		int op,x,y;
		scanf("%d%d%d",&op,&x,&y);
		if(x>n||y>n)
		{
			cnt++;
			continue;
		} 
		if(op==1)//x和y是同类
		{
			if(find(x+n)==find(y)||find(x)==find(y+n))//y在x的捕食域或者x在y的捕食域,矛盾
				cnt++;
			else
			{
				f[find(y)]=find(x);//合并x和y的自身域
				f[find(y+n)]=find(x+n);//合并x和y的捕食域
				f[find(y+2*n)]=find(x+2*n);	//合并x和y的被捕食域
			}
		}
		if(op==2)//x捕食y
		{
			if(find(x)==find(y)||find(x)==find(y+n))//y在x的自身域或者x在y的捕食域,矛盾
				cnt++;
			else 
			{
				f[find(y)]=find(x+n);//合并y的自身域和x的捕食域
				f[find(y+2*n)]=find(x);//合并y的被捕食域和x的自身域
				f[find(y+n)]=find(x+2*n);//合并y的捕食域和x的被捕食域
			}
		}
	}
	cout<<cnt<<endl;
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

春弦_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值