2020暑期D2-H 思路+证明
主为自用,欢迎指正。
https://ac.nowcoder.com/acm/contest/5667/H
前置知识:
题意:
给定一个集合MS和q种操作,操作共三种。
1.插入x。
2.删除x。
3.查找MS中是否存在a、b,使得a、b、x构成三角形。
能输出“Yes”,反之“No”。
思路:
首先,三个数之间的差越小,构成三角形的可能性就越大,由于存在插入与删除的操作,否则集合排序,搜索找相邻即可。故,一定要用树的结构来实现插入与删除,由于要实现数之间差最小,容易通过前后缀存入相邻数据。
设,a为b的前驱,若x为a、b中的最大边,若a+b>x,则成立;若x非最大边,则在任意b>=x的节点中,若存在x>b-a,则成立。
因此我们只需实现前驱相邻即可,但是这样反而像个一维数组,而不是一个树。。。
线段树每个节点通过包含一个区间的信息来快速得到区间所需信息,由于线段树的每个叶子节点存放输入数据,后自底向上遍历更新各区间节点信息。
而本体的线段树,增加了对最小值下标low、h1对大元素下标、h2区间次大元素下标、b区间相邻元素差最小值的维护,大体思路依照炮兵布阵即可。
代码步骤:
AC代码:
#define _CRT_SECURE_NO_WARNINGS
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 200010, INF = 0x3f3f3f3f;
vector<int> v;
int find(int x)
{
return lower_bound(v.begin(), v.end(), x) - v.begin(); //获得数值的排序位置。
}
struct Node {
int l, r;
int low, h1, h2, b;
int sz;
}tr[N << 4];
int op[N], x[N];
void build(int u, int l, int r)
{
tr[u] = { l, r, -1, -1, -1, INF, 0 };
if (l == r) return;
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
}
void pushup(Node& u, Node& left, Node& right) //自底向上充实树的值。
{
u.sz = left.sz + right.sz;
if (left.sz) u.low = left.low; else u.low = right.low; //自底向上理解,所有数值从左到右以叶子节点自小到大存储。
int h[4] = { left.h1, left.h2, right.h1, right.h2 };
sort(h, h + 4);
u.h1 = h[3], u.h2 = h[2];
u.b = min(left.b, right.b);
if (left.sz and right.sz) u.b = min(u.b, v[right.low] - v[left.h1]); //向量v是有序排列的,故v[right.low]与v[left.h1]相邻。
}
void pushup(int u)
{
pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
}
void modify(int u, int x, int c)
{
if (tr[u].l == x and tr[u].r == x) //叶子节点
{
tr[u].sz += c;·
if (tr[u].sz <= 0) tr[u].low = tr[u].h1 = tr[u].h2 = -1, tr[u].b = INF;
else if (tr[u].sz == 1) tr[u].low = tr[u].h1 = tr[u].l, tr[u].h2 = -1, tr[u].b = INF;
else tr[u].low = tr[u].h1 = tr[u].h2 = tr[u].l, tr[u].b = 0;
return; //主要是对最短距离的维护,因为都是固定的。
}
int mid = tr[u].l + tr[u].r >> 1;
if (x <= mid) modify(u << 1, x, c);
else modify(u << 1 | 1, x, c);
pushup(u); //在pushup()中实现真正的修改。
//printf("[%d, %d] sz = %d b = %d\n", tr[u].l, tr[u].r, tr[u].sz, tr[u].b);
}
Node query(int u, int l, int r)
{
if (tr[u].l >= l and tr[u].r <= r) return tr[u];//输出区间范围内的节点
int mid = tr[u].l + tr[u].r >> 1;
if (r <= mid) return query(u << 1, l, r);
if (l > mid) return query(u << 1 | 1, l, r);
Node left = query(u << 1, l, r), right = query(u << 1 | 1, l, r), res;
pushup(res, left, right);
return res;
}
int main()
{
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d%d", &op[i], &x[i]);
v.push_back(x[i]);
}
v.push_back(-1);
sort(v.begin(), v.end());
v.erase(unique(v.begin(), v.end()), v.end()); //删除v中重复的数。(unique 返回指向不重复区域之后一个位置的迭代器;erase 删除迭代器之间的元素)
build(1, 0, v.size() - 1);
for (int i = 1; i <= n; i++)
{
if (op[i] == 1) modify(1, find(x[i]), 1);
else if (op[i] == 2) modify(1, find(x[i]), -1); //节点sz-1使该节点无法在query中被获取
else
{
Node q1 = query(1, 0, find(x[i]) - 1);
if (q1.sz >= 2 and v[q1.h1] + v[q1.h2] > x[i]) //存在x为最大值,即有x有两个前继节点
{
puts("Yes");
continue;
}
Node q2 = query(1, find(x[i]), v.size() - 1);
//printf("q1.sz = %d q2.sz = %d\n", q1.sz, q2.sz);
if (q2.sz)
{
q1.b = INF;
Node res;
pushup(res, q1, q2);
//printf("q2.b = %d res.b = %d\n", q2.b, res.b);
if (x[i] > res.b)
{
puts("Yes");
continue;
}
}
puts("No");
}
}
return 0;
}
简单图证:
注:
应注意顺序,即注意查询时,已具有哪些数据。



京公网安备 11010502036488号