【Codeforces】Educational Codeforces Round 66 E. Minimal Segment | 贪心、并查集

55 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第28天,点击查看活动详情

题目链接

Problem - 1175E - Codeforces

题目

image.png

题目大意

[l,r][l,r] 的形式给出 nn 个区间,给出 qq 个询问,每个询问 [x,y][x,y] 要求回答最少使用多少个区间可以覆盖从 xxyy 的所有实数点,无解输出 -1。

思路

首先让我们考虑一个复杂度更高的方法:
首先,贪心地求出从起点 xx 开始,使用一个区间向右最远可以覆盖到哪个点。再把我们覆盖到的最远的点设置为起点,重复上述过程,直到将 yy 覆盖,或者确认无解。这种解法显然是正确的。

我们先预处理出对于以从 0 到 500000 中每个位置为起点用一个区间向右最远可以到达的位置,记作 fxf_x。这件事情很容易做:首先我们对于每个输入的区间 [l,r][l,r],使 fl=rf_l=r。然后从头到尾扫一遍 ff,如果 ii 的前一个位置 i1i-1 向右最远可以到达的位置比 ii 更远,即 fi1>fif_{i-1}>f_ifi1>if_{i-1}>i,就令 fi=fi1f_i=f_{i-1}。这样我们每次就可以 O(1) 求得下一个起点了。

但是我们还是需要多次遍历区间。我们用并查集来优化查询的过程:
让我们先把所有查询操作读取进来,离线处理。
把所有的边按其右边界的递增顺序排序,当我们处理查询时,如果本次从节点 ii 跳转没能到达右边界,我们就令从节点 ii 向右跳转的位置 fif_i 置为 ffif_{f_i},并将该跳转步数置为从 ii 跳转到 fif_i 和从 fif_i 跳转到 ffif_{f_i} 的步数之和。因为还未处理的查询的右边界一定在当前被查询的区间的右边界的右边,所以该合并一定不会改变后续查询求解的结果。

这样我们处理所有的查询,每个区间最多被“跳转未到达”一次。

代码

#include <bits/stdc++.h>
using namespace std;
const int N=500001;
struct asdf{
	int l,r,id;
}q[N];
int n,m,len;
int cmpr(asdf a,asdf b){return a.r<b.r;}
int f[N],ans[N],cst[N];
int main()
{
	len=N-1;
	scanf("%d%d",&n,&m);
	for (int i=1,l,r;i<=n;++i)
	{
		scanf("%d%d",&l,&r);
		f[l]=max(f[l],r);
	}
	cst[0]=1;
	for (int i=1;i<=len;++i)
	{
		f[i]=max(f[i],f[i-1]);
		if (f[i]<=i) f[i]=0;
		else cst[i]=1;
	}
	for (int i=1;i<=m;++i) scanf("%d%d",&q[i].l,&q[i].r),q[i].id=i;
	sort(q+1,q+1+m,cmpr);
	for (int i=1,t;i<=m;++i)
	{
		if (f[q[i].l]==0)
		{
			ans[q[i].id]=-1;
			continue;
		}
		while (f[q[i].l]<q[i].r)
		{
			if (f[f[q[i].l]]==0)
			{
				ans[q[i].id]=-1;
				break;
			}
			cst[q[i].l]+=cst[f[q[i].l]];
			f[q[i].l]=f[f[q[i].l]];
		}
		if (ans[q[i].id]==-1) continue;
		ans[q[i].id]=cst[q[i].l];
	}
	for (int i=1;i<=m;++i) printf("%d\n",ans[i]);
}