#310 (div.2) D. Case of Fugitive

1.题目描述:点击打开链接

2.解题思路:本题利用贪心法+优先队列解决。不过本题的贪心策略的选取是关键,有些看似正确的贪心策略实际上暗含危险。先说说正确的贪心策略:将所有的岛按照顺序求出第i个岛和i+1个岛之间桥的最小最大长度,并按照L从小到大排序,若相同则按照R从小到大排序。然后对桥由小到大排序,将所有的桥扫描一遍,枚举第i个桥时,将L值小于等于当前桥的区间按照(R,id)放入优先队列,R小的在队首,大的在队尾,每次看队首的R是否大于等于len,若满足,则记录答案,break,若不存在,则无解。接下来说几种经典的错误贪心策略。

错误一:将区间按照上述规则排序后,枚举区间,然后从小到大选择桥。反例:比如有2个区间,排序后是[2,31], [5,19], 桥由小到大排序后是 19,20 。按照该策略,会导致无解。


错误二:将区间按照右端点从小到大排序,若R相同,按照L从小到大排序,然后从小到大选择桥。 反例:比如有2个区间,排序后是[17,19], [2,31],桥由小到大排序后是2,17。按照该策略,会导致无解。


实际上,上述两个错误的原因的本质原因是一样的:区间端点的优先级实际上无法保证子问题得到最优解。然而为什么可以枚举桥呢?因为桥是按照长度由小到大依次枚举的,如果一个区间的L满足长度较短的桥,那么它也必然满足长度较大的桥,如果队尾的区间的R值依然小于len,那么由于后面的桥长度更大,R值更小于len了,因此这种贪心策略是正确的。

3.代码:

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<algorithm>
#include<cassert>
#include<string>
#include<sstream>
#include<set>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<deque>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<cctype>
#include<functional>
using namespace std;

#define me(s) memset(s,0,sizeof(s))
#define pb push_back
typedef long long ll;
typedef unsigned int uint;
typedef unsigned long long ull;
typedef pair <ll, int> P;


const int N = 200000 + 10;
ll x[N], y[N];
ll length[N];
int r[N];
ll ans[N];

struct Node
{
	int id;
	ll L,R;
	bool operator<(const Node&rhs)const
	{
		return L < rhs.L || (L == rhs.L&&R < rhs.R);
	}
}a[N];

int n, m;

bool cmp(int a, int b)
{
	return length[a] < length[b];
}

int main()
{
	while (~scanf("%d%d", &n, &m))
	{
		me(a); me(x); me(y); me(length); me(r);
		for (int i = 0; i<n; i++)
			scanf("%I64d%I64d", &x[i], &y[i]);
		for (int i = 0; i < m; i++)
		{
			scanf("%I64d", &length[i]);
			r[i] = i;
		}
		for (int i = 0; i<n - 1; i++)
		{
			a[i] = { i, x[i + 1] - y[i], y[i + 1] - x[i] };
		}
		sort(a, a + n - 1); sort(r, r + m, cmp);
		priority_queue<P, vector<P>, greater<P> >q;
		int cnt = 0;
		int j = 0;
		for (int i = 0; i < m; i++)
		{
			int p = r[i];
			for (; j < n-1;j++)
			if (a[j].L <= length[p])//将左端点满足的区间入队列,同时按照R值由小到大出队列
			{
				q.push(P(a[j].R, a[j].id));
			}
			else break;
			while (!q.empty())
			{
				if (q.top().first >= length[p])//如果队首的R值≥当前桥的长度,记录答案,否则无解(但此处没有直接break,而是改为用cnt来判断)
				{
					ans[q.top().second] = p; 
					cnt++; q.pop();
					break;
				}
				q.pop();
			}
		}
		if (cnt == n - 1)//如果答案的个数不是n-1,必然无解
		{
			puts("Yes");
			for (int i = 0; i<n - 1; i++)
				printf("%I64d%c", ans[i] + 1, " \n"[i == n - 2]);
		}
		else puts("No");
	}
}


你可能感兴趣的:(区间覆盖,贪心法)