李超线段树

题目描述

秋月十分擅长对空作战。尽管如此,必要的训练也不能懈怠。

在一次训练中,会有m个事件发生。
1. 出现一架飞机在(x1,y1)到(x2,y2)的线段上出现并来回飞行。
2. 秋月接到指令,要在x坐标为a处击坠一架飞行路线经过该位置的飞机。
为了确保击坠的可能性最大,秋月将选择在x位置时高度最低的飞机。

由于只是训练任务,秋月并不会真正地击坠飞机。你只需要在每次接到指令时告诉她高度最低的飞机的y坐标就可以了。


题解

这道题就要用到所谓的李超线段树, 主要的思想就是计算出每个线段的最优势线段, 最优势线段就是指, 从当前线段的x轴往上看, 暴露得最多的就是最优的, 所以说高度最低的一定是在某条又是线段里面, 每次插入维护一下就行了, 靠新开两个域, 分别是当前优势线段在当前线段 l 处的高度, 和在r处的高度, 就能判断新线段和就线段谁才是最优势线段. 查询递归取max即可. x坐标的范围是1e9的, 注意动态开空间. 
上面讲的只是大概, 代码里面有详解, 很清晰, 能看懂.
#include<stdio.h>
#include<cmath>
#include<algorithm>
using namespace std;
const double eps = 1e-8;
const int lg = 30;
const int maxn = 200005;
const int inf  = 1e9;
int n, id, root;
inline const int read(){
	register int x = 0;
	register char ch = getchar();
	while(ch < '0' || ch > '9') ch = getchar();
	while(ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
	return x;
}
struct node{
	int l, r;
	double lv, rv;
}tr[maxn * lg];
inline double calc(int x, int x1, double y1, int x2, double y2){
	return (y2 - y1) * (x - x1) / (x2 - x1) + y1;
}
inline void modify(int &rt, int lf, int rg, int x1, double y1, int x2, double y2){
	if(!rt) rt = ++id, tr[rt].lv = inf, tr[rt].rv = inf; //没有线段, 那左右端点的最优线段的高度值初始化为
	int mid = (lf + rg) >> 1;
	if(x1 <= lf && rg <= x2){//如果覆盖了当前线段区间 
		double lv = calc(lf, x1, y1, x2, y2), rv = calc(rg, x1, y1, x2, y2); //计算新线段在左右端点的高度 
		double sl = lv - tr[rt].lv, sr = rv - tr[rt].rv;
		if(sl <= eps && sr <= eps){ //说明了当前新插入的在当前线段(线段树区间)左右端点处的都要比原来的优势线段小, 
									//那么肯定暴露得最多. 
			tr[rt].lv = lv;//直接修改 
			tr[rt].rv = rv;
			return;
		}
		if(sl >= -eps && sr >= -eps) return;//比原来的都高, 没有用 
		if(fabs(sl) < fabs(sr)){//这里说明新老交叉, 可以画一下, 图上是两个交叉的线段
		 						//这里是靠一个相似三角形来判断哪个暴露的更多 
			if(sr <= eps){ //如果是负数的话说明当前线段优 
				modify(tr[rt].l, lf, mid, lf, tr[rt].lv, rg, tr[rt].rv); //先把旧的递归下去更新
																		//这样保证把每条线段都用的淋漓尽致 
				tr[rt].lv = lv; //更新当前最优线段, 如果查询的位置在这个区间里的话一定会计算这条最优线段的 
				tr[rt].rv = rv;//可以看看query辅助理解 
			} else
				modify(tr[rt].l, lf, mid, x1, y1, x2, y2); //新老交点在左边,由于不如老的优,所以用武之地只在左边 
		} else {
			if(sl <= eps){ //说明当前线段优 
				modify(tr[rt].r, mid + 1, rg, lf, tr[rt].lv, rg, tr[rt].rv); //同上 
				tr[rt].lv = lv;
				tr[rt].rv = rv;	
			} else
				modify(tr[rt].r, mid + 1, rg, x1, y1, x2, y2);
		}
	} else {///不然就递归处理 
		if(x1 <= mid) 
			modify(tr[rt].l, lf, mid, x1, y1, x2, y2);
		if(x2 > mid)
			modify(tr[rt].r, mid + 1, rg, x1, y1, x2, y2);
	}
}
double query(int rt, int lf, int rg, int pos){
	if(!rt) return inf;
	if(lf == rg) return tr[rt].lv;
	int mid = (lf + rg) >> 1;
	double ans = calc(pos, lf, tr[rt].lv, rg, tr[rt].rv);//计算高度 
	if(pos <= mid) 
		return min(ans, query(tr[rt].l, lf, mid, pos));
	else 
		return min(ans, query(tr[rt].r, mid + 1, rg, pos));
}
int main(){
	freopen("defense.in", "r", stdin);
	freopen("defense.out", "w", stdout);
	n = read();
	int x1, y1, x2, y2;
	for(register int i = 1; i <= n; ++i){
		int opt = read();
		if(opt == 1){
			x1 = read(), y1 = read(), x2 = read(), y2 = read();
			modify(root, 1, inf, x1, y1, x2, y2); //插入 
		}else
			x1 = read(), printf("%0.4lf\n", query(root, 1, inf, x1));
	}
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值