CSP-J 2020 入门级 第一轮 完善程序(2)

本文介绍了一个CSP-J2020入门级竞赛的编程题目,要求使用贪心法解决最小区间覆盖问题。题目中给出了n个区间,目标是找出最少数量的区间来覆盖[0,m]。解题思路是首先对区间按左端点升序排序,然后依次选择包含当前关注点(初始为0)的区间中右端点最大的。代码中包含了排序和选择区间的实现细节,最终输出选择的区间数量。
摘要由CSDN通过智能技术生成

【题目】

CSP-J 2020 入门级 第一轮 完善程序(2)
(最小区间覆盖)给出 n 个区间,第 i 个区间的左右端点是 [ a i , b i ] [a_i,b_i] [ai,bi]。现在要在这些区间中选出若干个,使得区间 [ 0 , m ] [0, m] [0,m]被所选区间的并覆盖(即每一个 0 ≤ i ≤ m 0\leq i\leq m 0im都在某个所选的区间中)。保证答案存在,求所选区间个数的最小值。
输入第一行包含两个整数 n 和 m ( 1 ≤ n ≤ 5000 1\le n \le 5000 1n5000, 1 ≤ m ≤ 1 0 9 1\le m \le 10^9 1m109)
接下来 n 行,每行两个整数 a i , b i a_i,b_i ai,bi 0 ≤ a i , b i ≤ m 0\le a_i,b_i \le m 0ai,bim
提示:使用贪心法解决这个问题。先用 O ( n 2 ) O(n^2) O(n2) 的时间复杂度排序,然后贪心选择这些区间。

试补全程序。

#include <iostream>

using namespace std;

const int MAXN = 5000;
int n, m;
struct segment { int a, b; } A[MAXN];

void sort() // 排序
{
	for (int i = 0; i < n; i++)
		for (int j = 1; j < n; j++)
			if ()
		    {
		    	segment t = A[j];}
}

int main()
{
	cin >> n >> m;
	for (int i = 0; i < n; i++)
		cin >> A[i].a >> A[i]?b;
	sort();
	int p = 1;
	for (int i = 1; i < n; i++)
		if ()
     		A[p++] = A[i];
	n = p;
	int ans =0, r = 0;
	int q = 0;
	while (r < m)
	{
		while ()
			q++;;
		ans++;
	}
	cout << ans << endl;
	return 0;
}
  1. ①处应填( )
    A. A[j].b>A[j-1].b
    B. A[j].a<A[j-1].a
    C. A[j].a>A[j-1].a
    D. A[j].b<A[j-1].b
  2. ②处应填( )
    A. A[j+1]=A[j];A[j]=t;
    B. A[j-1]=A[j];A[j]=t;
    C. A[j]=A[j+1];A[j+1]=t;
    D. A[j]=A[j-1];A[j-1]=t;
  3. ③处应填( )
    A. A[i].b>A[p-1].b
    B. A[i].b<A[i-1].b
    C. A[i].b>A[i-1].b
    D. A[i].b<A[p-1].b
  4. ④处应填( )
    A. q+1<n&&A[q+1].a<=r
    B. q+1<n&&A[q+1].b<=r
    C. q<n&&A[q].a<=r
    D. q<n&&A[q].b<=r
  5. ⑤处应填( )
    A. r=max(r,A[q+1].b)
    B. r=max(r,A[q].b)
    C. r=max(r,A[q+1].a)
    D. q++

【题目考点】

1. 贪心

区间覆盖问题

【解题思路】

贪心求最小区间覆盖问题。
关注点:上一次选择的区间的右端点,初值为0。
贪心选择:在所有包含关注点的区间中,选择右端点最大的区间。

贪心选择性质的证明:
证明:最优解包含第一次的贪心选择:在所有包含0的区间中,选择右端点最大的区间
第0位置总该被包含到某个区间中。如果选择的区间不包括0,那么无法完成区间覆盖。
这一次选择的,也就是唯一包含第0位置的区间,记该区间为 a g a_g ag
假设存在一组最优解不包含贪心选择: a 1 , a 2 , . . . , a n a_1, a_2, ..., a_n a1,a2,...,an是选择的区间, a 1 a_1 a1~ a n a_n an按左端点从小到大排序,其中没有 a g a_g ag
其中 a 1 a_1 a1中一定包含第0位置,否则如果 a 1 a_1 a1的左端点大于0,后面的区间左端点都大于 a 1 a_1 a1的左端点,第0位置就不会被任何区间包含,这就不是一组解了。
a g a_g ag替换 a 1 a_1 a1,得到 a g , a 2 , . . . , a n a_g, a_2, ..., a_n ag,a2,...,an a 1 a_1 a1 a g a_g ag都包括第0位置,而 a g a_g ag的右端点大于 a 1 a_1 a1的右端点,替换后一定可以覆盖整个区间。
因此 a g , a 2 , . . . , a n a_g, a_2, ..., a_n ag,a2,...,an也是该问题的一组最优解。得到了包含贪心选择的最优解,假设不成立,原命题得证。
证明:在最优解包含前k次的贪心选择的情况下,存在最优解包含第k+1次的贪心选择
证明方法同上。把“第0位置”变为“第k次贪心选择的右端点”即可。

具体做法为:

  1. 先将所有区间按照左端点从小到大进行排序,关注点初值为0。
  2. 按顺序遍历所有区间,如果该区间的左端点小于等于关注点,则该区间包含关注点。
  3. 继续向后遍历,得到所有包含关注点的区间中,右端点最大的区间。
  4. 选择该区间,选择区间数量加1,将关注点设为该区间的右端点
  5. 最后输出选择区间的数量

明确算法后,下面看代码:

const int MAXN = 5000;
int n, m;
struct segment { int a, b; } A[MAXN];
//...
int main()
{
	cin >> n >> m;
	for (int i = 0; i < n; i++)
		cin >> A[i].a >> A[i]?b;

segment类型用来表示一个区间,区间的左端点为属性a,右端点为属性b。

void sort() // 排序
{
	for (int i = 0; i < n; i++)
		for (int j = 1; j < n; j++)
			if ()
		    {
		    	segment t = A[j];}
}

接下来是排序,看形式是冒泡排序。i指的是冒泡次数,每次冒泡能确定一个数字的位置。j指的是当前比较数对中的第二个数字。也就是A[j-1]和A[j]进行比较。
要想选择“包含关注点的区间”,应该按区间的左端点从小到大进行排序。排序后应该满足A[j-1].a <= A[j].a,如果不满足该条件,则要交换A[j-1]A[j]
①处应该填不满足A[j-1].a <= A[j].a的条件,即A[j-1].a > A[j].a,选B。
②处是交换两个变量的写法,要交换的是A[j]A[j-1]。把A[j]赋值给临时变量t后,应该把A[j-1]赋值给A[j],再把刚才保存在t中的值赋值给A[j-1]。选D。

	int p = 1;
	for (int i = 1; i < n; i++)
		if ()
     		A[p++] = A[i];
	n = p;

明显这是数组填充的过程,p是数组中已有的元素个数,填充到下标0~p-1。最后又让n变为填充后数组A中的元素个数。
第0个区间直接加入数组A。
这里是选择数组A中的部分数据,再填充到数组A。实际是删掉了A中的一些元素。
p是再次填充后A中元素(区间)的个数。A[p-1]是上一次确定的区间。一定有 p-1 <= i。
当前A数组中的元素已经按照左端点排序,设里面存在两个元素,那么一定有A[p-1].a <= A[i].a
如果A[i].b <= A[p-1].b,那么A[i]就完全是A[p-1]的子区间,或者说A[p-1]完全覆盖了A[i],而且覆盖的区间可能更大。如果存在最优解要选择A[i],那么可以用覆盖A[i]A[p-1]来替换A[i]。因此就没有必要考虑A[i]了,可以把A[i]删掉,也就是不把A[i]再次填充进数组A。
反过来,如果A[i].b > A[p-1].b,就应该把A[i]再次填充进数组A。
③处应该选A
这样做的结果是,经过再次填充后的数组A,关于右端点A[i].b也是升序的。

	int ans =0, r = 0;
	int q = 0;
	while (r < m)
	{
		while ()
			q++;;
		ans++;
	}

ans是选择的区间数量,r为上文提到的关注点(上一个选择的区间的右端点),q是数组A的下标,初值为0。 [ 0 , m ] [0, m] [0,m]是要被覆盖的区间。
只要关注点小于整个区间右端点m,那么应该遍历经过再次填充得到的A数组

  • 如果第q+1区间左端点小于等于关注点r,就让q增加1,看下一个区间。
    A数组下标从0~n-1,要保证q+1小于n,不能超过数组长度。
    因此④处填q+1<n && A[q+1].a <= r
  • 如果当前第q+1区间左端点大于关注点r,那么应该选择第q区间,该区间的右端点一定是包含关注点r的区间中右端点的最大值。把第q区间的右端点设为关注点r。
    应该执行r = A[q].b
    第⑤处,题目中给的是r = max(r, A[q].b)的效果与r = A[q].b相同,因为A数组已经关于A[i].b升序,r顺序地取区间的右端点,一定有A[q].b >= r,所以两个表达式效果相同。

【答案】

  1. B
  2. D
  3. A
  4. A
  5. B
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值