UVA11020 Efficient Solutions
题目
见链接。
题解
知识点:STL,二分。
首先不考虑新加入的操作,先考虑一个固定的局面,给定了所有人的两个属性值 \(L\) 和 \(C\),找出有竞争力的人。
这类双变量的条件一般可以画在坐标系 \(L-C\) 查看,发现如果一个点坐标到原点构成的矩形内部(包括边,但不包括这个点本身)有其他点,则这个点就没有竞争力。
于是发现竞争力人群规律, \(L\) 小的人相对于其他人 \(C\) 会较大,而 \(L\) 大的人相对于其他人 \(C\) 会较小。因为对于一个 \(L\) 小的人,那么后面 \(L\) 大的人 \(C\) 要比 \(L\) 小的人的 \(C\) 小,不然就没竞争力,所以整体会呈现一个反比例函数的形式。
接下来,容器要满足排序,插入,删除,查找,且元素可能重复,因此用 multiset
。为了方便查找,我们对列表按 \(L\) 为第一关键字从小到大排, \(C\) 为第二关键字从小到大排。
现在考虑插入一个人,我们先在坐标系中往他左边看,判断他能不能插入。具体的说,我们要找到 \(L\) 小于他的人中 \(C\) 最小的人,这个人是最有可能使他失去优势的人。根据我们之前分析的特性, \(L\) 越大 \(C\) 越小,因此需要找到最后一个 \(L\) 小于他或者 \(L\) 等于他但 \(C\) 小于等于他的人。为此,我们需要使用 lower_bound
找到第一个 \(L\) 大于他或者 \(L\) 等于他但 \(C\) 大于等于他的人,这个人的前一个人就是我们要找的人。如果没有找到,或者找到的人 \(C\) 大于他,那么他就能插入,否则不能。
如果能插入,我们在坐标系中往他右边看,开始排除后面需要踢掉的人。具体的说,我们要踢掉所有 \(L\) 大于他且 \(C\) 大于等于他或者 \(L\) 等于他且 \(C\) 大于他的人。根据我们之前分析的特性, \(L\) 越大 \(C\) 越小,因此从第一个 \(L\) 大于他或者 \(L\) 等于他但 \(C\) 大于他的人开始踢,我们可以用 upper_bound
实现。随后开始向后遍历,我们只要踢到第一个 \(C\) 小于他的人之前即可,后面的 \(C\) 更小不需要考虑。
最后容器大小就是有竞争力的人的人数。
时间复杂度 \(O(n \log n)\)
空间复杂度 \(O(n)\)
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
struct node {
int x, y;
friend bool operator<(node a, node b) {
return a.x == b.x ? a.y < b.y : a.x < b.x;
}
};
bool solve() {
int n;
cin >> n;
multiset<node> ms;
for (int i = 0, x, y;i < n;i++) {
cin >> x >> y;
node a = { x,y };
auto it = ms.lower_bound(a);//找到最有可能使a失去优势的人
if (it == ms.begin() || (--it)->y > y) {//a有优势
it = ms.insert(a);
it = ms.upper_bound(a);//第一个没有优势的人
while (it != ms.end() && it->y >= y) it = ms.erase(it);
}
cout << ms.size() << '\n';
}
return true;
}
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
int cnt = 1;
while (t--) {
cout << "Case #" << cnt++ << ":" << '\n';
if (!solve()) cout << -1 << '\n';
cout << (t ? "\n" : "");
}
return 0;
}
本文来自博客园,作者:空白菌,转载请注明原文链接:https://www.cnblogs.com/BlankYang/p/16461452.html