题解 LGP8101【[USACO22JAN] Multiple Choice Test P】

problem

奶牛们正在参加一个选择题测试。在通常的测试中,对每个问题你的选项会被单独评分然后累加,而在此测试中,你的选项在累加之后再评分。

具体地说,你被给定二维平面上的 \(N\)\(2 \le N \le 10^5\))组整数向量,其中每个向量用一个有序对 \((x,y)\) 表示。从每组中选择一个向量,使向量的总和尽可能远离原点。

输入保证向量的总数不超过 \(2 \times 10^5\)。每组至少包含 \(2\) 个向量,并且一组内所有向量各不相同。输入同时保证每个 \(x\)\(y\) 坐标的绝对值不超过 \(\dfrac{10^9}{N}\)

solution

这个题,观察到“向量的总和尽可能远离原点”这句话,如果只关心一组的向量,那么可能成为答案的向量是这个向量集的凸包,因为可以感性理解一下凸包里的点不会比凸包上的点答案更劣,凸包内的点加了其他向量也不如它到原点连线到凸包的交点,就是说只关心凸包上的点。

然后合并凸包可以用 Minkowski / 闵可夫斯基和。合并差分数组。

凸包求法:Graham

首先取一个 \(x\) 坐标最小,如果有多个取 \(y\) 坐标最小,的点,这个点是一定在凸包上的。以这个点为原点,对所有点做极角排序,然后做斜率优化(迫真)。就是维护栈,栈中的点连成的多边形始终保持斜率单调。

code

点击查看代码

观察到 minkowski 和的过程是合并差分,因此我们将所有凸包的差分拿出来一起排序。



#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <cassert>
#include <algorithm>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr,##__VA_ARGS__)
#else
#define debug(...) void(0)
#endif
typedef long long LL;
template<class T> struct vec{
    T x,y;
    vec operator+(const vec&rhs){return {x+rhs.x,y+rhs.y};}
    vec operator-(const vec&rhs){return {x-rhs.x,y-rhs.y};}
    vec operator*(const T&k){return {x*k,y*k};}
    friend T cross(const vec&lhs,const vec&rhs){return lhs.x*rhs.y-lhs.y*rhs.x;}
    friend T operator*(const vec&lhs,const vec&rhs){return lhs.x*rhs.x+lhs.y*rhs.y;}
    friend bool quad(const vec&self){return self.x<=0&&self.y<0||self.x<0&&self.y>=0;}
    bool operator<(const vec&rhs){return x!=rhs.x?x<rhs.x:y<rhs.y;}
    friend LL dist(const vec&self){return 1ll*self.x*self.x+1ll*self.y*self.y;}
};
typedef vec<double> vec_t;
vector<vec_t> convexHull(vector<vec_t> pts){
    if(pts.empty()) return {};
    static vec_t stk[1<<19]; int top=0;
    vec_t cen=*min_element(pts.begin(),pts.end());
    sort(pts.begin(),pts.end(),[&](vec_t a,vec_t b){
        if(quad(a-cen)!=quad(b-cen)) return quad(a-cen)<quad(b-cen);
        if(cross(a-cen,b-cen)) return cross(a-cen,b-cen)>0;
        return dist(a-cen)<dist(b-cen);
    });
    for(vec_t v:pts){
        while(top>=2&&cross(stk[top-1]-stk[top],v-stk[top])>0) top--;
        stk[++top]=v;
    }
    return vector<vec_t>(stk+1,stk+top+1);
}
int main(){
//  #ifdef LOCAL
//      freopen("input.in","r",stdin);
//  #endif
    int n;
    scanf("%d",&n);
    vector<vec_t> pts(n);
    for(vec_t&v:pts) scanf("%lf%lf",&v.x,&v.y);
    vector<vec_t> res=convexHull(pts);
    double ans=0;
    for(int i=0;i<res.size();i++) ans+=dist(res[i]-res[(i+1)%res.size()]);
    printf("%.2lf\n",ans);
    return 0;
}

当然也可以启发式合并。


#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#include <cassert>
#include <utility>
#include <algorithm>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr,##__VA_ARGS__)
#else
#define debug(...) void(0)
#endif
typedef long long LL;
template<class T> using pqueue = priority_queue<T, vector<T>, greater<T>>;
template<class T> struct dot_t {
    T x, y;
    dot_t operator+(const dot_t& b) const { return {x + b.x, y + b.y}; }
    dot_t operator-(const dot_t& b) const { return {x - b.x, y - b.y}; }
    dot_t operator*(const T& k) const { return {x * k, y * k}; }
    T operator*(const dot_t& b) const { return x * b.x + y * b.y; }
    T friend cross(const dot_t& a, const dot_t& b) {
        return a.x * b.y - a.y * b.x;
    }
    bool friend cmp(const dot_t& a, const dot_t& b) {
        return cross(a, b) ? cross(a, b) > 0 : a.x < b.x;
    }
    bool friend operator<(const dot_t& a,const dot_t& b) {
        return a.x != b.x ? a.x < b.x : a.y < b.y;
    }
};
typedef dot_t<LL> dot;
vector<dot> convexHull(vector<dot> a) {
    static dot stk[1 << 18];
    dot cen = *min_element(a.begin(), a.end());
    sort(a.begin(), a.end(),
        [&](const dot& a,const dot& b) { return cmp(a - cen, b - cen); });
    int top = 0;
    for (dot v: a){
        while (top >= 2 && cross(stk[top - 1] - stk[top], v - stk[top]) > 0)
            top--;
        stk[++top] = v;
    }
    return vector<dot>(stk + 1, stk + top + 1);
}
vector<dot> minkowski(const vector<dot>& a, const vector<dot>& b) {
    vector<dot> c = {a[0] + b[0]};
    static dot sa[1 << 18], sb[1 << 18];
    int n = a.size(), m = b.size();
    for (int i = 0; i < n; i++)
        sa[i] = a[(i + 1) % n] - a[i]; 
    for (int i = 0; i < m; i++)
        sb[i] = b[(i + 1) % m] - b[i]; 
    int i = 0, j = 0;
    for (int k = 1; k < n + m; k++){
        if (i < n && (j >= m || cmp(sa[i], sb[j])))
            c.push_back(c.back() + sa[i++]);
        else
            c.push_back(c.back() + sb[j++]);
    }
    return c;
}
int n;
vector<dot> a[1 << 18];
pqueue<pair<int, int>> q;
int main() {
    scanf("%d", &n);
    for (int i = 1, k; i <= n; i++) {
        scanf("%d", &k);
        vector<dot> pts(k);
        for (dot& v: pts)
            scanf("%lld%lld", &v.x, &v.y);
        a[i] = convexHull(pts);
        q.push({a[i].size(), i});
    }
    for (int i = 1; i < n; i++) {
        int u = q.top().second; q.pop();
        int v = q.top().second; q.pop();
        a[u] = minkowski(a[u], a[v]);
        q.push({a[u].size(), u});
    }
    LL ans = -1e18;
    for (dot v: a[q.top().second])
        ans = max(ans, v * v);
    printf("%lld\n",ans);
    return 0;
}


posted @ 2023-09-15 19:39  caijianhong  阅读(9)  评论(0编辑  收藏  举报