1 条题解

  • 0
    @ 2025-12-1 18:41:23

    题解

    把方向向量记作 vecd\\vec d,当两条平行线的法向为 vecd\\vec d 时,所有点在 \\vec d^\\perp 上的投影构成一条数轴,求“某一段连续区间的权值和最大”。也就是说,只要知道当前方向下点的投影顺序,就能通过最大子段和得到答案;当方向旋转时,投影顺序只会在相邻的两点投影重合时发生交换。

    因此采用“旋转扫描 + 线段树维护最大子段和”的常规套路:

    1. 初始方向设为 xx 轴正向,按 (x,y)(x,y) 排序作为初始投影顺序,在线段树上建权值数组。
    2. 对每一对点 (i,j)(i,j),当方向与向量 (xjxi,yjyi)(x_j-x_i, y_j-y_i) 垂直时,二者投影相等,此时顺序会交换。向量的垂直向量记作 (Deltay,Deltax)(-\\Delta y, \\Delta x),把它化成有向单位(同向归一)后作为事件的键。
    3. 将所有事件按键排序,角度相同的一组一起处理;在该角度附近,只有这些事件对应的点对可能交换。旋转的连续性保证它们在处理时已经相邻,因此只要检查是否相邻即可执行交换,同时在线段树做两次单点修改。组处理完更新一次答案。

    线段树节点维护区间和、前缀最大、后缀最大、子段最大,交换两个相邻位置仅需两次单点更新。事件数为 mathcalO(N2)\\mathcal{O}(N^2),整体复杂度 mathcalO(N2logN)\\mathcal{O}(N^2 \\log N),内存 mathcalO(N)\\mathcal{O}(N),可满足 Nle2000N\\le 2000

    #include <bits/stdc++.h>
    using namespace std;
    
    struct Node { long long sum, pref, suff, best; };
    struct SegTree {
        int n; vector<Node> st;
        SegTree(const vector<long long>& a){ n=a.size(); st.resize(4*n); build(1,0,n-1,a); }
        Node merge(const Node& L,const Node& R){
            return {L.sum+R.sum,
                    max(L.pref, L.sum+R.pref),
                    max(R.suff, R.sum+L.suff),
                    max({L.best,R.best,L.suff+R.pref})};
        }
        void build(int p,int l,int r,const vector<long long>& a){
            if(l==r){ long long v=a[l]; st[p]={v,v,v,v}; return; }
            int m=(l+r)>>1; build(p<<1,l,m,a); build(p<<1|1,m+1,r,a); st[p]=merge(st[p<<1],st[p<<1|1]);
        }
        void update(int p,int l,int r,int idx,long long v){
            if(l==r){ st[p]={v,v,v,v}; return; }
            int m=(l+r)>>1; if(idx<=m) update(p<<1,l,m,idx,v); else update(p<<1|1,m+1,r,idx,v); st[p]=merge(st[p<<1],st[p<<1|1]);
        }
        void update(int idx,long long v){ update(1,0,n-1,idx,v); }
        long long best() const { return st[1].best; }
    };
    
    struct Point{ long long x,y,w; };
    struct Event{ long long vx,vy; int a,b; };
    
    int main(){
        ios::sync_with_stdio(false); cin.tie(nullptr);
        int N; if(!(cin>>N)) return 0;
        vector<Point> p(N); for(int i=0;i<N;++i) cin>>p[i].x>>p[i].y>>p[i].w;
    
        vector<int> order(N); iota(order.begin(),order.end(),0);
        sort(order.begin(),order.end(),[&](int a,int b){ return p[a].x==p[b].x ? p[a].y<p[b].y : p[a].x<p[b].x; });
        vector<int> pos(N); vector<long long> cur(N);
        for(int i=0;i<N;++i){ pos[order[i]]=i; cur[i]=p[order[i]].w; }
        SegTree seg(cur); long long ans=seg.best();
    
        vector<Event> ev; ev.reserve(1LL*N*(N-1)/2);
        for(int i=0;i<N;++i) for(int j=i+1;j<N;++j){
            long long dx=p[j].x-p[i].x, dy=p[j].y-p[i].y;
            long long vx=-dy, vy=dx; long long g=std::gcd(std::llabs(vx), std::llabs(vy)); if(g){vx/=g; vy/=g;}
            if(vy<0 || (vy==0 && vx<0)){vx=-vx; vy=-vy;}
            ev.push_back({vx,vy,i,j});
        }
        auto half=[](long long x,long long y){ return y>0 || (y==0 && x>0); };
        auto cmp=[&](const Event& A,const Event& B){
            bool ha=half(A.vx,A.vy), hb=half(B.vx,B.vy);
            if(ha!=hb) return ha<hb;
            return A.vy*B.vx < A.vx*B.vy;
        };
        sort(ev.begin(),ev.end(),cmp);
    
        size_t i=0;
        while(i<ev.size()){
            size_t j=i;
            while(j<ev.size() && ev[j].vx==ev[i].vx && ev[j].vy==ev[i].vy) ++j;
            for(size_t k=i;k<j;++k){
                int a=ev[k].a, b=ev[k].b;
                int pa=pos[a], pb=pos[b];
                if(pa>pb){ swap(pa,pb); swap(a,b); }
                if(pb-pa==1){
                    swap(order[pa],order[pb]); swap(cur[pa],cur[pb]);
                    pos[a]=pb; pos[b]=pa;
                    seg.update(pa,cur[pa]); seg.update(pb,cur[pb]);
                }
            }
            ans=max(ans, seg.best());
            i=j;
        }
        cout<<ans<<'\n';
        return 0;
    }
    
    • 1

    信息

    ID
    5716
    时间
    1000ms
    内存
    256MiB
    难度
    8
    标签
    递交数
    3
    已通过
    1
    上传者