bzoj3533: [Sdoi2014]向量集

本文介绍了一种使用线段树和三分法处理动态凸包查询问题的方法。通过避免频繁重构凸包,实现了高效的时间复杂度。文章详细解释了如何通过线段树维护动态插入点,并利用三分法查询最优解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

链接

  http://www.lydsy.com/JudgeOnline/problem.php?id=3533

题解

  QAQ以后再也不乱写模板了,因为Granham的各种错误查了1天,所有错误都是Graham里的…
  通过做这道题目学会了三分。
  就是l,mid1,mid2,r,可以三分出单峰函数的极值,循环条件是(r-l>=3)。
  通过这道题还发现一个事实,如果每个线段树上的节点都存 O(size) 大小的数据,那么总内存占用只是 O(NlogN) 级别的。这个事实可以帮助我们做这道题目。
  把向量看成坐标系中的点,对于一次查询 Q(xq,yq) ,如果 yq>0 ,画图可知,答案在上凸壳上。(连接 (0,0)(xq,yq) 然后做垂线,假设一个点是 P(xp,yp) ,移动垂线使得 OP OQ 上的投影最大)不知道说的明白不明白,反正就那个意思…然后就发现答案就是从无穷远处移动垂线碰到的第一个点。当 yq>0 时,答案在上凸壳上,否则在下凸壳上(=0的情况归为哪一类都行)
  如果给你一个凸壳,想要求查询的答案的话,显然答案是单峰的(画图可知),所以就可以三分。
  现在要解决的问题是询问的区间 [l,r] ,直接通过线段树分配到某几个区间上,一次查询的复杂度是 log2
  问题在于,怎么添加点。
  显然单点插入,每个点只会插入 logN 个节点里,如果每次暴力 Graham Scan 重构的话,一次插入的复杂度就成了 NlogN ,显然太慢。
  容易看到,只有插入操作,而且每次都是在末端插入,一个询问会被线段树分配成若干个区间,这些区间显然都是完整的、所有元素都被插入过的。
  也就是说,一个线段树上的点只有所有的元素被插入了之后,才有被查询到的可能。那么,当我们插入这个节点上的最后一个节点的时候,再调用 Graham Scan 构建凸壳。显然每个节点都只会构造一次凸包。
  这样的复杂度是 O(Nlog2N+Qlog2N) 的。
  如果把 Graham Scan 做凸包改成斜率优化那样的单调栈维护凸壳,复杂度会变成 O(NlogN+Qlog2N)

代码

//线段树+凸包+三分 
#include <cstdio>
#include <algorithm>
#include <queue>
#include <vector>
#include <cmath>
#define ll long long
#define maxn 400005
#define inf ((long long)1<<60)
using namespace std;
ll T, Mi, lastans, ndtot;
struct vec{ll x, y;}tmp[maxn], s[maxn];
struct segtree
{
    ll l, r, jie;
    segtree *ch[2];
    vector<vec>pt;
}pool[maxn<<2], *root;
inline vec operator-(vec &v1, vec &v2){return (vec){v1.x-v2.x,v1.y-v2.y};}
inline ll operator*(vec v1, vec v2){return v1.x*v2.y-v2.x*v1.y;}
inline ll cp(vec v1, vec v2){return v1.x*v2.x+v1.y*v2.y;}
inline bool operator<(vec v1, vec v2)
{
    v1=v1-s[1], v2=v2-s[1];
    return v1*v2==0?v1.x*v1.x+v1.y*v1.y<v2.x*v2.x+v2.y*v2.y:v1*v2>0;
}
inline ll read(ll x=0)
{
    char c=getchar(); ll f=1;
    while(c<48 or c>57)f=c=='-'?-1:1,c=getchar();
    while(c>=48 and c<=57)x=(x<<1)+(x<<3)+c-48, c=getchar();
    x*=f;
    if(Mi)x^=(lastans&0x7fffffff);
    return x;
}
inline void graham(segtree *p)
{
    ll i, j, top=1, sz=p->r-p->l+1;
    for(i=1;i<=sz;i++)tmp[i]=p->pt[i-1];
    for(j=1,i=2;i<=sz;i++)
        if(tmp[i].x<tmp[j].x or tmp[i].x==tmp[j].x and tmp[i].y<tmp[j].y)j=i;
    s[1]=tmp[j];swap(tmp[1],tmp[j]);
    sort(tmp+2,tmp+sz+1);
    for(i=2;i<=sz;i++)
    {
        while(top>1 and (tmp[i]-s[top])*(s[top]-s[top-1])>=0)top--;
        s[++top]=tmp[i];
    }
    s[++top]=s[1];
    p->pt.clear();
    for(i=1;i<=top;i++)p->pt.push_back(s[i]);
    for(i=1;i<top;i++)if(s[i+1].x<=s[i].x)break;
    p->jie=i-1;
}
inline ll solve(segtree *p, vec pt)
{
    ll l, r, mid1, mid2;
    ll ans=-inf;
    if(pt.y>0)l=p->jie,r=p->pt.size()-1;
    else l=0,r=p->jie;
    while(r-l>=3)
    {
        mid1=l+(r-l)/3, mid2=r-(r-l)/3;
        if(cp(pt,p->pt[mid1])>cp(pt,p->pt[mid2]))r=mid2;else l=mid1;
    }
    for(ll i=l;i<=r;i++)ans=max(ans,cp(pt,p->pt[i]));
    return ans;
}
void segins(segtree *p, ll pos, vec pt)
{
    p->pt.push_back(pt);
    if(pos==p->r)graham(p);
    if(p->l==p->r)return;
    ll mid=(p->l+p->r)>>1;
    if(pos<=mid)segins(p->ch[0],pos,pt);
    if(pos>mid)segins(p->ch[1],pos,pt);
}
ll segmax(segtree *p, ll l, ll r, vec pt)
{
    ll mid=(p->l+p->r)>>1;
    ll ans=-inf;
    if(l<=p->l and r>=p->r)return solve(p,pt);
    if(l<=mid)ans=max(ans,segmax(p->ch[0],l,r,pt));
    if(r>mid)ans=max(ans,segmax(p->ch[1],l,r,pt));
    return ans;
}
void build(segtree *p, ll l, ll r)
{
    ll mid=(l+r)>>1;
    p->l=l, p->r=r;
    if(l==r)return;
    build(p->ch[0]=pool+ ++ndtot,l,mid);
    build(p->ch[1]=pool+ ++ndtot,mid+1,r);
}
void work()
{
    char s[10];
    ll i, l, r, T=0, N;
    vec pt;
    N=read();scanf("%s",s);Mi=s[0]!='E';
    build(root=pool+ ++ndtot,1,N);
    for(i=1;i<=N;i++)
    {
        scanf("%s",s);
        pt.x=read(),pt.y=read();
        if(s[0]=='Q')
        {
            l=read(),r=read();
            printf("%lld\n",lastans=segmax(root,l,r,pt));
        }
        else{segins(root,++T,pt);}
    }
}
int main()
{
    work();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值