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

简单图证:

注:
应注意顺序,即注意查询时,已具有哪些数据。