YbtOJ 躲避拥挤(并查集)

这篇博客介绍了如何利用并查集解决一道图论问题,通过对数据进行排序和路径压缩,维护集合大小来求解。同时,文章还强调了在处理过程中需要考虑双向通道的情况,并使用一个ans数组来确保正确输出顺序。

目录

前置知识

题目描述

样例与数据范围

解题思路

AC 代码


前置知识:

        并查集 (路径压缩),快速排序(sort),结构体

题目描述:

样例与数据范围:

解题思路:

        在并查集的专题里,当然是用并查集啦!

        刚开始的时候,小编还以为是用搜索的 qwq (你看这个题,他多像搜索啊),再读一读题目,n 个点,m 条边,这很明显是个图论题啊(废话,并查集不就是图论的一部分吗),再想一想,利用并查集维护图的连通性,建立一个 sum 数组维护集合大小,就可以完美解决了。

        为了使用时方便,可以先将原数据按照人气值 d 从小到大排个序。

        不难发现(是真的不难),排序后在前面的询问能经过的边在后面的边一定能经过。所以我们可以按顺序连边(所以,要将 q 组询问的数据按照 x 从小到大的顺序排个序),对于每一个询问加入能够加入的边,利用并查集维护图的连通性,用 sum 数组维护集合大小。对于加入的一条边 ,若 a , b 在同一集合中,则可以直接忽略;否则对答案的贡献为 sum[a] * sum[b]。

        因为是双向通道,二并查集的统计方式之后统计一次(也就是说(a , b) 和 (b , a) 两种法案,只会统计一次,所以不要忘记将答案乘2。

        因为将询问数据排序后可能会让输出的顺序变乱,所以要建立一个 ans 数组来存储每次询问的答案

AC 代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<map>
#include<vector>
#include<string>
#include<cstring>
#include<queue>//头文件不解释
using namespace std;
int test,Q,n,m,tot;//test是数据的组数,Q是每组数据中的询问次数,n是景点数,m是边数
int fa[100001];//记录其祖先元素
int sum[100001];//用于维护集合的大小
int ans[100001];//储存每次询问的答案 
struct no//记录原始数据
{
	int x;//出发景点
	int y;//到达景点
	int d;//人气值
}a[100001];
struct de//记录询问的数据
{
	int x;//记录要求不能超过的人气值的大小
	int num;//记录第几次询问 
}q[100001];
bool cmp1(no a,no b)//将原始数据按从小到大的顺序排个序 
{
	return a.d<b.d;
}
bool cmp2(de a,de b)//将询问数据也按从小到大的顺序排个序
{
	return a.x<b.x;
}
int find(int x)//并查集基本操作,寻找该节点的祖先
{
	if(!fa[x]) 
	{
		return x;
	}
	return fa[x]=find(fa[x]);
}
void add(int x,int y)//并查集基本操作,合并两个集合,不过要穿差一点东西,以到达维护sum数组的目的
{
	int u=find(x);//寻找x祖先元素
	int v=find(y);//寻找y祖先元素
	if(u==v)//如果两个元素已在同一个集合,则直接忽略
	{
		return;
	}
	tot+=sum[u]*sum[v]*2;//否则,这两个点对答案的贡献就是sum[u]*sum[v]*2(不要忘记乘2)
	fa[u]=v;//合并两个集合
	sum[v]+=sum[u];//维护sum数组
    sum[u]=0;//这里写不写都可以(只要你对并查集理解得够深,就能明白)
}
int main()
{
	scanf("%d",&test);//输入test
	while(test--)//test次循环
	{
		scanf("%d%d%d",&n,&m,&Q);//读入
		for(int i=1;i<=m;i++)//读入
		{
			scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].d);
		}
        sort(a+1,a+m+1,cmp1);//给原始数据排序
        for(int i=1;i<=Q;i++)//读入
        {
        	scanf("%d",&q[i].x);//读入
        	q[i].num=i;//记录一下是第几次询问
		}
		sort(q+1,q+1+Q,cmp2);//给询问数据排序
		tot=0;//记录景点的对数
		for(int i=1;i<=n;i++)//并查集基本操作之一————初始化
		{
			fa[i]=0;
			sum[i]=1;//初始时每个元素都是一个集合,所以sum数组的值都为1
		}				
		int j=1;//记录当前询问到第几组道路
		for(int i=1;i<=Q;i++)
		{
			while((j<=m)&&(a[j].d<=q[i].x))
			{
				add(a[j].x,a[j].y);//合并两个集合
				j++;//询问数加1
			}
			ans[q[i].num]=tot;//记录答案
		}
		for(int i=1;i<=Q;i++)//输出
		{
			printf("%d\n",ans[i]);
		}
    }
    return 0;//完结撒花
}

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值