【BZOJ1568】[JSOI2008] Blue Mary开公司(卡精度线段树)
大致题意: 两种操作:新给你一条斜率大于\(0\)的直线;给定横坐标(整数),问已知直线中最大的\(y\)值。
卡精度
绝望的卡精度。。。
可能是我的做法和别人不太一样,看了下题解似乎都是李超线段树(不知道这是什么神仙东西),甚至还有一个暴力水过的,好像就我一个人写线段树上二分 emmm
在BZOJ上卡精度卡了一个晚上都没能过,第二天到机房接着卡,又花了一个半小时才过了。
结果到洛谷上又\(WA\)了,只有\(70\)分,但实在是不想调,只好扔在那里,假装我过了。
当然,或许并不是精度炸了,而是写挂了,如果真是这样,希望能帮我指出。
线段树上二分
考虑每条直线的答案区间,最后发现必然长成这个样子:
即每条直线斜率递增,且必然对应一段区间的答案。
然后考虑新加入一条直线,如果对答案有贡献,就应该是这个样子的:
对于这种情况,可以发现必然是超过一条斜率较小的直线,最终又被斜率较大的直线超越。
所以我们就可以线段树上二分出它能作为答案的左右区间。
应该是挺好理解的。
至于二分的细节可以详见代码注释。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define DB long double
using namespace std;
int n;DB k[N+5],b[N+5];
class SegmenTree
{
private:
#define PT CI l=0,CI r=n-1,CI rt=1
#define LT l,mid,rt<<1
#define RT mid+1,r,rt<<1|1
#define PU(x) (O[x].L=O[x<<1].L,O[x].R=O[x<<1|1].R)
#define PD(x) O[x].id&&(T(x<<1,O[x].id),T(x<<1|1,O[x].id),O[x].id=0)
#define T(x,p) (O[x].id=O[x].L=O[x].R=p)
#define f(p,x) (k[p]*(x)+b[p])
struct node {int id,L,R;}O[N<<2];
I int FL(CI p,PT)//找左端点
{
if(f(O[rt].L,l)<=f(p,l)&&f(O[rt].R,r)<=f(p,r)) return l;
//如果左端点和右端点的值都较小,说明肯定这整段区间都被新直线碾压,直接返回左端点
if(l==r||(f(O[rt].L,l)>f(p,l)&&k[O[rt].L]>=k[p])) return -1;
//如果到了单点,或者左端点的值比新直线大,且斜率更大,说明新直线永远无法超越原答案,返回-1
RI mid=l+r>>1;PD(rt);if(k[O[rt<<1|1].L]>k[p]) return FL(p,LT);
//因为要保证斜率递减,所以若右区间斜率不满足限制就去搞左区间
return f(O[rt<<1].R,mid)<=f(p,mid)?FL(p,LT):FL(p,RT);
//尽可能走左区间
}
I int FR(CI p,PT)//找右端点,和上面大致一样,不再重复
{
if(f(O[rt].L,l)<=f(p,l)&&f(O[rt].R,r)<=f(p,r)) return r;
if(l==r||(f(O[rt].L,l)>f(p,l)&&k[O[rt].L]>=k[p])) return -1;
RI mid=l+r>>1;PD(rt);if(k[O[rt<<1].R]<k[p]) return FR(p,RT);
return f(O[rt<<1|1].L,mid+1)<=f(p,mid+1)?FR(p,RT):FR(p,LT);
}
I void U(CI x,CI y,CI p,PT)//区间修改为新直线
{
if(x<=l&&r<=y) return (void)T(rt,p);RI mid=l+r>>1;PD(rt);
x<=mid&&(U(x,y,p,LT),0),y>mid&&(U(x,y,p,RT),0),PU(rt);
}
public:
I void U(CI p)//修改
{
RI l=k[O[1].L]<=k[p]?FL(p):0,r=k[O[1].R]>=k[p]?FR(p):n-1;//注意没有斜率更小/斜率更大的直线的边界
~l&&~r&&l<=r&&(U(l,r,p),0);
}
I int Q(CI x,PT)//询问
{
if(l==r||O[rt].id) return O[rt].id;
RI mid=l+r>>1;return x<=mid?Q(x,LT):Q(x,RT);
}
}S;
int main()
{
char s[10];DB x,y;for(RI i=(scanf("%d",&n),1),t=0,p,q;i<=n;++i)
{
if(scanf("%s",s),s[0]=='P') ++t,scanf("%Lf%Lf",&b[t],&k[t]),S.U(t);//修改
else scanf("%d",&q),p=S.Q(q-1),printf("%d\n",(int)floor((k[p]*(q-1)+b[p])/100));//询问
}return 0;
}
待到再迷茫时回头望,所有脚印会发出光芒