Happy Triangle
题目大意
有
次操作
![]()
表示在数组中添加上
表示从数组中删除
问你能否从数组中选出两个数
使得这三个数可以构成一个三角形
解题思路
这题好像好多解法吧,因为是可以离线进行操作的,很巧的是没有一个是本菜鸡会的。赛后看了看大佬们的博客才知道,原来线段树还可以这样操作呀,真的是张见识了。
因为三个数如果想要构成三角形那么必须要满足 任意两个数之和大于第三个数。因此我们可以先对输入的 进行离散化处理后,用线段树维护一个区间中的一些值
我们需要维护区间最大值,次大值,最小值的位置,和区间最小差的值,这样比较方便进行下面的操作。 假设
那么我们只需要查找比 小的那部分区间的最大的两个值即可,这样如果是
那么肯定可有构成三角形
假设
那么的话我们只需要找到比 大的区间中两个数的最小差值大于
即可
如果都不满足的话那么就肯定不能构成三角形了
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int mx=200200;
const int inf=0x3f3f3f3f;
vector<int>ve;
struct node{
int l,r;// 表示这个区间的左右边界
//表示这个区间的 最小值,最大值,次小值的 离散后的数组下标
//区间的数据中相差最小值
int low,h1,h2,b;
int sz;//这个区间的元素个数
}tr[mx<<2];
//存操作数 和操作数后面的值
int op[mx],a[mx];
int n;
//获取离散化前面的值
int get_id(int x){
return lower_bound(ve.begin(),ve.end(),x)-ve.begin();
}
//建树
void build(int x,int l,int r){
tr[x]={l,r,-1,-1,-1,inf,0};
if(l==r) return;
int mid=l+r>>1;
build(x<<1,l,mid);
build(x<<1|1,mid+1,r);
}
// 更新节点 这里用引用类型的好处是不用写返回值了 可直接更新
void push_up(node &x,node &left,node &right){
x.sz=left.sz+right.sz;//这个区间的值等于左右两个之和
//最小值肯定在这两个区间中的一个里面
if(left.sz){
x.low=left.low;
}else{
x.low=right.low;
}
// 更新这段区间的最大值和次大值
int h[4]={left.h1,left.h2,right.h2,right.h1};
sort(h,h+4);
x.h1=h[3],x.h2=h[2];
//更新相差最小值
x.b=min(left.b,right.b);
if(left.sz and right.sz){
//注意因为线段树存的是离线化以后的下标
//进行比较的时候需要用离散化前的值
x.b=min(x.b,ve[right.low]-ve[left.h1]);
}
}
void push_up(int x){
push_up(tr[x],tr[x<<1],tr[x<<1|1]);
}
void update(int x,int pos,int k){
if(tr[x].l==pos and tr[x].r==pos){
tr[x].sz+=k;
if(tr[x].sz<=0){
tr[x].h1=tr[x].h2=tr[x].low=-1;
tr[x].b=inf;
}else if(tr[x].sz==1){
tr[x].h1=tr[x].low=tr[x].l;
tr[x].h2=-1;
tr[x].b=inf;
}else{
//如果se大于1说明有相同的数出现了所以最大值最小值都是一样的
//差值最小肯定是0
tr[x].low=tr[x].h1=tr[x].h2=tr[x].l;
tr[x].b=0;
}
return;
}
int mid=tr[x].l+tr[x].r>>1;
if(pos<=mid) update(x<<1,pos,k);
else update(x<<1|1,pos,k);
push_up(x);
}
node query(int x,int l,int r){
if(tr[x].l>=l and tr[x].r<=r){
return tr[x];
}
int mid=tr[x].l+tr[x].r>>1;
if(r<=mid) return query(x<<1,l,r);
if(l>mid) return query(x<<1|1,l,r);
// 这一步相当于 l 和 r 占据了 x的左区间一点,右区间一点
node left=query(x<<1,l,r);
node right=query(x<<1|1,l,r);
node res;
push_up(res,left,right);
return res;
}
int main(){
ios::sync_with_stdio(0);
cin>>n;
for(int i=1;i<=n;i++){
cin>>op[i]>>a[i];
ve.push_back(a[i]);
}
//数组下标是1 开始的所以加个最小值
ve.push_back(-1);
//离散化处理
sort(ve.begin(),ve.end());
ve.erase(unique(ve.begin(),ve.end()),ve.end());
build(1,0,ve.size()-1);
for(int i=1;i<=n;i++){
if(op[i]==1){
update(1,get_id(a[i]),1);
}else if(op[i]==2){
update(1,get_id(a[i]),-1);
}else{
// 如果找到第一个小于 a[i] 的数 a<b<z;
// 如果 x+y>x 那么肯定可以
node q1=query(1,0,get_id(a[i])-1);
if(q1.sz>=2&&ve[q1.h1]+ve[q1.h2]>a[i]){
cout<<"Yes\n";
continue;
}
// x<a<b
node q2=query(1,get_id(a[i]),ve.size()-1);
if(q2.sz){
//为了计算 比 x 的那部分区间差值最小是多少;
//防止左区间的 差值 影响右区间所以这里给左区间赋个极大值
q1.b=inf;
node res;
push_up(res,q1,q2);
//如果最大值的差小于 x 那么肯定也是可以的 b-a<x
if(a[i]>res.b){
cout<<"Yes\n";
continue;
}
}
cout<<"No\n";
}
}
return 0;
} 
京公网安备 11010502036488号