《GMOJ-Junior-2471 【GDKOI2021 普及组 Day1】旅行》题解

题目大意

给出一个有 n n n个点、 m m m条无向边,可能有重边、自环的图,经过图中的每条边都要花费一些钱,但是允许花 p p p p p p为任意正整数)的价格购买一张优惠券,使得可以免费通过 花 费 ≤ p 花费 \leq p p的所有边。现在有 q q q个询问,每个询问给出 x , p x,p x,p,问从 x x x号点出发,花费不超过 p p p时,最多能访问的节点数。
对于 20 % 20\% 20%的数据, n , m , q ≤ 5000 n,m,q \leq 5000 n,m,q5000
对于 50 % 50\% 50%的数据, n , q ≤ 5000 n,q \leq 5000 n,q5000 m ≤ 4 × 1 0 5 m \leq 4 \times 10^5 m4×105
对于 100 % 100\% 100%的数据, n , q ≤ 2 × 1 0 5 n,q \leq 2 \times 10^5 n,q2×105 m ≤ 4 × 1 0 5 m \leq 4 \times 10^5 m4×105 0 ≤ 经 过 一 条 边 的 花 费 ≤ 1 0 9 0 \leq 经过一条边的花费 \leq 10^9 0109

分析

这是一道用贪心的思想解决的题。
首先,我们得发现一个事实:当我们把每次询问的 p p p全部拿去买一张优惠券时,得到的答案是最优的。因为当我们花 a a a买一张优惠券后还要花 b b b b > a b>a b>a)再走一条边时,我们就可以直接花 b b b买一张优惠券。
于是,我们可以将所有询问离线,并将所有边和所有询问按花费或 p p p从小到大排序。然后我们顺序扫描排完序的序列,并用并查集维护当前每个连通块的大小。如果我们扫描到一条边,那么我们就合并这条边所连接的两个连通块;如果我们扫描到一个询问,我们就记录当前询问的 x x x所在连通块的大小作为该询问的答案。最后输出所有答案即可。

代码

根据我们的思路,可以写出如下代码:

#include<cstdio>
#include<algorithm>
using namespace std;
char buf[16777216];
inline int Read() //快速读入
{
	static char* c = buf;
	while (*c < '0' || *c>'9')
	{
		++c;
	}
	int ans = *c ^ 48;
	while (*(++c) >= '0' && *c <= '9')
	{
		ans = ans * 10 + (*c ^ 48);
	}
	return ans;
}
struct Action //将边和询问排序用,若存的是边,则compare_val为花费,other_val1和other_val2为连接的两个点;若存的是询问,则compare_val为q,other_val1为询问编号的相反数,other_val2为x
{
	int compare_val, other_val1, other_val2;
	struct Compare
	{
		inline bool operator()(const Action a, const Action b) //比较函数(注意要让compare_val相同的询问排在边后)
		{
			return a.compare_val < b.compare_val || (a.compare_val == b.compare_val && (a.other_val1 > 0 && b.other_val1 < 0));
		}
	};
}act[600006];
int group[200002]; //并查集
inline int Group(const int pos)
{
	if (group[pos] == pos)
	{
		return pos;
	}
	group[pos] = Group(group[pos]);
	return group[pos];
}
int node[200002]/*每个连通块的大小*/, ans[200002]/*答案*/;
int main()
{
	static_cast<void>(freopen("travel.in", "r", stdin)); //定义文件输入输出
	static_cast<void>(freopen("travel.out", "w", stdout));
	fread(buf, 1, 16777216, stdin); //读入输入
	const int n = Read(), m = Read(); //读入n和m
	for (int i = 1; i <= m; ++i) //读入每一条边
	{
		act[i].other_val1 = Read();
		act[i].other_val2 = Read();
		act[i].compare_val = Read();
	}
	const int q = Read(); //读入q
	for (int i = 1; i <= q; ++i) //读入每一个询问
	{
		const int pos = m + i;
		act[pos].other_val1 = -i;
		act[pos].other_val2 = Read();
		act[pos].compare_val = Read();
	}
	const int size = m + q;
	sort(act + 1, act + (size + 1ll), Action::Compare()); //排序
	for (int i = 1; i <= n; ++i) //初始化并查集
	{
		group[i] = i;
		node[i] = 1;
	}
	int q_count = 0; //对已处理的询问的计数
	for (int i = 1; i <= size; ++i) //顺序扫描
	{
		if (act[i].other_val1 < 0) //扫描到一个询问
		{
			ans[-act[i].other_val1] = node[Group(act[i].other_val2)]; //记录答案
			if ((++q_count) == q) //若处理完所有询问则退出
			{
				break;
			}
		}
		else //扫描到一条边
		{
			const int a = Group(act[i].other_val1), b = Group(act[i].other_val2); //合并连通块
			if (a != b)
			{
				group[a] = b;
				node[b] += node[a];
			}
		}
	}
	for (int i = 1; i <= q; ++i) //输出答案
	{
		printf("%d\n", ans[i]);
	}
	return 0;
}

总结

这道题结合了贪心思想和并查集,是一道不错的题目。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值