bzoj3533【SDOI2014】向量集 (线段树 凸包 三分)

有一个结论:答案一定会出现在凸包上,而且如果y>0则在上凸包上,否则在下凸包上。
简单证明:要计算一个向量a和一些向量的点积的最大值。因为所有向量都是共起点的,所以只要找到这些向量终点中在向量a的方向最靠前的,而这个最靠前点一定在凸包上。
然后在一个凸包上答案是单峰的,可以用三分来做。
于是问题就变成了如何动态维护区间的凸包,这个可以用线段树。
可是问题在于每次新加一个点会修改logn段区间,而凸包合并是O(n),显然这个复杂度不能接受。
有一个比较巧妙的解决方法,包含未插入位置的线段树节点一定不会被访问(这显然),所以每插入一个点x,只要将区间右端点是x的凸包合并就可以了。

对于上面那个结论,如图
在凸包范围内红点为与所给向量点积最大的向量
因为红点所代表的向量可以分解为一个与所给向量水平的分量以及一个垂直的分量相加的结果
而垂直分量与所给向量的点积为零,于是可以把向量组中的向量全部分解为一个与所给向量水平的分量以及一个垂直的分量,拥有水平分量最大的向量即为最优解
可以想象一条直线从所给向量指向的无限远向凸包靠近,第一个接触到的点即为最优解
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

我的代码
#include <bits/stdc++.h>
#define pb push_back
#define memarray(array,val) memset(array,val,sizeof(array))
using namespace std;
const int mod=1e9+7;
const double PI=acos(-1.0);
const double EPS=5e-15;
inline int sgn(double a){
    if(a<-EPS)return -1;
    return a>EPS;
}
inline int cmp(double a,double b){
    return sgn(a-b);
}
const int MAXN=4e5+10;
const int INF=0x3f3f3f3f;
int n,m;
struct Point{
    int x,y;
    Point(int _x=0.0,int _y=0.0){
        x=_x;y=_y;
    }
    double len(){
        return sqrt(len2());
    }
    double len2(){
        return x*x+y*y;
    }
    bool operator<(const Point p)const{
        if(cmp(x,p.x)==0)return y<p.y;
        return x<p.x;
    }
    Point operator+(const Point p)const{
        return (Point){x+p.x,y+p.y};
    }
    Point operator-(const Point p)const{
        return (Point){x-p.x,y-p.y};
    }
    long long operator*(const Point p)const{
        return 1LL*x*p.x+1LL*y*p.y;
    }
    long long operator^(const Point p)const{
        return 1LL*x*p.y-1LL*y*p.x;
    }
};
char s[5];
inline int decode (int x,long long lastans) {
    return x^(lastans & 0x7fffffff);
}
vector<Point>vecup[MAXN<<2];
vector<Point>vecdown[MAXN<<2];
void merge(vector<Point>&vec,vector<Point>&p1,vector<Point>&p2,int op){
    vector<Point>::iterator it1=p1.begin(),it2=p2.begin();
    int len=0;
    Point point;
    while(it1!=p1.end()||it2!=p2.end()){
        if(it1==p1.end()){point=*it2;it2++;}
        else if(it2==p2.end()){point=*it1;it1++;}
        else{
            if(*it1<*it2){
                point=*it1;
                it1++;
            }else{
                point=*it2;
                it2++;
            }
        }
        if(op==0) {
            while (len >= 2 && ((vec[len - 1] - vec[len - 2]) ^ (point - vec[len - 2])) >= 0) {
                vec.pop_back();
                len--;
            }
        }else {
            while (len >= 2 && ((vec[len - 1] - vec[len - 2]) ^ (point - vec[len - 2])) <= 0) {
                vec.pop_back();
                len--;
            }
        }
        vec.pb(point);
        len++;
    }
}
void insert(Point p,int idx,int l,int r,int rt){
    if(l==r){
        vecup[rt].pb(p);
        vecdown[rt].pb(p);
        return;
    }
    int mid=(l+r)>>1;
    if(idx<=mid)insert(p,idx,l,mid,rt<<1);
    else insert(p,idx,mid+1,r,rt<<1|1);
    if(idx==r){//如果是区间的最右端则构造凸包
        merge(vecup[rt],vecup[rt<<1],vecup[rt<<1|1],0);
        merge(vecdown[rt],vecdown[rt<<1],vecdown[rt<<1|1],1);
    }
}
long long check(vector<Point>&vec,Point point){
    int l=0,r=vec.size()-1;
    while(l<r-5){
        int mid1=(l+r)/2;//这种三分写法会比下面这种写法快一点
        int mid2=mid1+1;
//        int mid1=(l+l+r)/3;
//        int mid2=(l+r+r)/3;
        long long a=vec[mid1]*point;
        long long b=vec[mid2]*point;
        if(a>b)r=mid2;
        else l=mid1;
    }
    long long ret=-1e18;
    for(int i=l;i<=r;i++){
        ret=max(ret,vec[i]*point);
    }
    return ret;
}
long long Query(Point p,int L,int R,int l,int r,int rt){
    long long ret=-1e18;
    if(L<=l&&R>=r){
        if(p.y>0)ret=check(vecup[rt],p);//查上凸壳
        else ret=check(vecdown[rt],p);//查下凸壳
        return ret;
    }
    int mid=(l+r)>>1;
    if(L<=mid) ret=max(ret,Query(p,L,R,l,mid,rt<<1));
    if(R>mid) ret=max(ret,Query(p,L,R,mid+1,r,rt<<1|1));
    return ret;
}
int query[MAXN][6];
void solve() {
    int x,y,l,r;
    long long lastans=0;
    int now=0;
    for(int i=1;i<=m;i++){
        x=query[i][1];
        y=query[i][2];
        if(query[i][0]==0){
            if(s[0]!='E'){
                x=decode(x,lastans);
                y=decode(y,lastans);
            }
            now++;
            insert((Point){x, y},now,1,n,1);
        }else{
            l=query[i][3];
            r=query[i][4];
            if(s[0]!='E'){
                x=decode(x,lastans);
                y=decode(y,lastans);
                l=decode(l,lastans);
                r=decode(r,lastans);
            }
            lastans=Query((Point){x, y},l,r,1,n,1);
            printf("%lld\n",lastans);
        }
    }
}
void init(){
    scanf("%d%s",&m,s);
    char op[5];
    int x,y,l,r;
    for(int i=1;i<=m;i++){
        scanf("%s",op);
        if(op[0]=='A'){
            scanf("%d%d",&x,&y);
            query[i][0]=0;
            query[i][1]=x;
            query[i][2]=y;
            n++;
        }else{
            scanf("%d%d%d%d",&x,&y,&l,&r);
            query[i][0]=1;
            query[i][1]=x;
            query[i][2]=y;
            query[i][3]=l;
            query[i][4]=r;
        }
    }
}
int main()
{
    int T=1;
    while(T--)
    {
        init();
        solve();
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值