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