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");
}
}