目录

[LOJ 6213]「美团 CodeM 决赛」radar

迁移提示
本文迁移自博客园, 原文链接

[LOJ 6213]「美团 CodeM 决赛」radar

题意

给定 $n$ 个横坐标 $x_i$ , 为它们选择一个不超过 $y_i$ 的纵坐标 $h_i$, 产生 $c_ih_i$ 的花费. 选择之后产生的总价值是所有以 $(x_i,h_i)$ 到 $x$ 轴的垂线段为斜边上的高的等腰直角三角形的并的面积. 最大化总价值与总花费之间的差并输出这个差.

$n\le 1\times 10^5$.

题解

首先有一个比较显然的沙雕性质, 就是某个三角形的高要么是 $0$ 要么是 $y_i$. 因为假设已经选择了一些三角形, 要求选择这个点之后对最后答案的贡献, 必然是一个下凸的二次函数. 显然这样的函数的区间最大值只能取在端点.

其次有另一个显然但是容易被忽略的性质, 就是最优解中不存在一个三角形被另一个三角形完全包含. 也就是说当计算选中一个新的三角形的贡献的时候不可能会出现一些鬼畜边界线的情况考试时成功忽略这个性质于是没敢写

因为不可能被完全包含, 所以我们可以按照三角形在 $x$ 轴上覆盖的右端点升序排序, 定义 $dp_i$ 表示前 $i$ 个三角形中选中最后一个三角形时能产生的最大答案. 因为不难发现唯一需要出现的浮点数是 $\frac 1 4$, 于是我们计算答案的 $4$ 倍, 这样就有三种转移情况.

定义 $l_i,r_i$ 分别为三角形在 $x$ 轴上覆盖的左右端点, $w_i$ 为 $4(y_i^2-c_iy_i)$ :

$$ dp_i=\max_{j

其中第一种转移显然是个前缀 $\max$, 因为已经按 $r_i$ 排过序了所以直接二分找到最后一个做这种贡献的位置然后取前缀 $\max$ 就可以了.

第二种转移需要拆一拆…

$$ \begin{aligned} dp_i&=\max_{j

其中 $\max$ 里面是一个一次函数的形式, 可以用李超树来搞.

等一下!

李超树怎么处理 $r_j\ge l_i$ 这个限制呢? 换句话说, 我们怎么在计算第二部分贡献的时候排除 $r_j<l_i$ 的点的贡献呢?

其实不用排除因为如果某个点同时做贡献的话第二种贡献是要小于第一种的233

于是直接李超树上就可以了. 总时间复杂度 $O(n\log n)$.

参考代码

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#include <bits/stdc++.h>

const int MAXN=1e5+10;
typedef long long intEx;

struct Point{
	int l;
	int r;
	intEx w;
};
Point P[MAXN];

int n;
int s[MAXN];
intEx smax[MAXN];

struct Line{
	intEx k;
	intEx b;
	Line(const intEx& a,const intEx& b):k(a),b(b){}
	inline intEx operator()(const int& x)const{
		return k*s[x]+b;
	}
};

struct Node{
	int l;
	int r;
	Line f;
	Node* lch;
	Node* rch;
	~Node();
	Node(int,int);
	intEx Query(int);
	void Insert(Line);
};

int main(){
	freopen("radar.in","r",stdin);
	freopen("radar.out","w",stdout);
	int T;
	scanf("%d",&T);
	while(T--){
		scanf("%d",&n);
		for(int i=1;i<=n;i++){
			int x,y,c;
			scanf("%d%d%d",&x,&y,&c);
			P[i].l=x-y;
			P[i].r=x+y;
			s[i]=P[i].l;
			P[i].w=4ll*y*y-4ll*c*y;
		}
		std::sort(P+1,P+n+1,
			[](const Point& a,const Point& b){
				return a.r<b.r;
			}
		);
		std::sort(s+1,s+n+1);
		int cnt=std::unique(s+1,s+n+1)-s-1;
		Node* N=new Node(1,cnt);
		for(int i=1;i<=n;i++){
//			printf("[%d,%d] w=%lld\n",P[i].l,P[i].r,P[i].w);
			int h=std::lower_bound(s+1,s+cnt+1,P[i].l)-s;

			intEx dp=N->Query(h)-1ll*P[i].l*P[i].l+P[i].w;

			int p=std::lower_bound(P+1,P+n+1,P[i].l,
				[](const Point& a,const int& b){
					return a.r<b;
				}
			)-P-1;
			dp=std::max(smax[p]+P[i].w,dp);

			N->Insert(Line(2*P[i].r,dp-1ll*P[i].r*P[i].r));
			smax[i]=std::max(smax[i-1],dp);
		}
		printf("%lld.%02lld\n",smax[n]>>2,(smax[n]&3)*25);
		delete N;
	}
	return 0;
}

intEx Node::Query(int x){
	if(this->l==this->r)
		return this->f(x);
	else{
		if(x<=this->lch->r)
			return std::max(this->lch->Query(x),this->f(x));
		else
			return std::max(this->rch->Query(x),this->f(x));
	}
}

void Node::Insert(Line f){
	int mid=(this->l+this->r)>>1;
	if(f(mid)>this->f(mid))
		std::swap(f,this->f);
	intEx ld=this->f(this->l)-f(this->l);
	intEx rd=this->f(this->r)-f(this->r);
	if(ld>=0&&rd>=0)
		return;
	else if(rd>=0)
		this->lch->Insert(f);
	else
		this->rch->Insert(f);
}

Node::~Node(){
	if(this->lch)
		delete this->lch;
	if(this->rch)
		delete this->rch;
}

Node::Node(int l,int r):l(l),r(r),f(0,-1e18),lch(NULL),rch(NULL){
	if(l!=r){
		int mid=(l+r)>>1;
		this->lch=new Node(l,mid);
		this->rch=new Node(mid+1,r);
	}
}

https://pic.rvalue.moe/2021/08/02/254b4cc06255f.png