题目大意: 有 n n n 次操作,每次操作有两种:1、增加一条直线;2、询问 x = T x=T x=T 时,在所有直线上 y y y 值的最大值。
题解
经典的李超线段树做法在这里,这篇博客讲另外一种cdq分治的做法。
脑补一下不难发现,最后对答案可能有贡献的那些直线,即处于最上方的那些直线,一定是一个下凸包的形状,像这样:
假如按时间顺序加入的直线的斜率递增,那么就可以很方便地维护出这个凸包,但题目并不保证这一点。
具体来说,一条直线能对一个询问产生贡献,需要满足: t i < t j t_i<t_j ti<tj。
而我们希望直线按斜率递增的顺序出现,即 k i < k j k_i<k_j ki<kj。
诶,那不是正好可以用cdq分治来做吗?
做法也很简单,将操作以时间顺序排好然后分治。每次递归完左右区间后,将左区间内的直线按斜率递增排序,然后一条一条加入单调队列。接着就可以很容易地更新右区间内的询问了。
当然,可以将右区间内的询问也进行排序,那么求解会变得更加简单,排序可以在分治时直接归并排序排好,时间复杂度 O ( n log n ) O(n\log n) O(nlogn)。
但是由于浮点数运算次数较多所以跑的并不算快,在吸了氧后跑到了 938 m s 938ms 938ms。
代码如下:
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
#define maxn 100010
#define eps 1e-8
int n;
struct par{
int type;double k,b;int pos;}a[maxn],tmp[maxn];
struct line{
double k,b;}q[maxn];
int st,ed;
double meet(line x,line y){
return (y.b-x.b)/(x.k-y.k);}
int dcmp(double x){
return x>eps?1:x<-eps?-1:0;}
void cdq(int l,int r){
if(l==r)return;
int mid=l+r>>1;cdq(l,mid);cdq(mid+1,r);
//求解
q[st=ed=1]=(line){
0,0};
for(int i=l;i<=mid;i++)if(!a[i].type){
line now=(line){
a[i].k,a[i].b};
while(ed>0&&dcmp(q[ed].k-now.k)==0&&dcmp(q[ed].b-now.b)<0)ed--;//特判斜率相同的直线
while(ed>1&&dcmp(meet(q[ed],q[ed-1])-meet(q[ed],now))>0)ed--;
q[++ed]=now;
}
for(int i=mid+1;i<=r;i++)if(a[i].type){
while(st<ed&&a[i].k-meet(q[st],q[st+1])>eps)st++;
a[i].b=max(a[i].b,q[st].b+q[st].k*a[i].k);
}
//归并
int x=l,y=mid+1,now=0;
while(x<=mid&&y<=r){
if(dcmp(a[x].k-a[y].k)<0)tmp[++now]=a[x++];
else tmp[++now]=a[y++];
}
while(x<=mid)tmp[++now]=a[x++];
while(y<=r)tmp[++now]=a[y++];
for(int i=l;i<=r;i++)a[i]=tmp[i-l+1];
}
bool cmp(par x,par y){
return x.pos<y.pos;}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++){
char s[10];scanf("%s",s);
if(s[0]=='P')a[i].type=0,scanf("%lf %lf",&a[i].b,&a[i].k),a[i].b-=a[i].k;
else a[i].type=1,scanf("%lf",&a[i].k);
a[i].pos=i;
}
cdq(1,n);
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++)
if(a[i].type)printf("%d\n",(int)a[i].b/100);
}