(2017多校训练第七场)HDU - 6127 Hard challenge 极角排序+尺取法

首先如果我们知道了一条直线左边和右边的点,只需要将左边点的value值的求和乘以右边点的value的值的求和即可。

对于每个点极角排序是显然的事情。

排完序之后,我们获得一圈点,很容易发现一条直线会把这个圆圈切割成两半,所以只需要枚举两个切割点即可。

枚举切割点的方法是尺取法

假设所有点的value值的求和是tot

对于区间的左端点L,我们不断的增加R,直到R和L的极角差刚刚小于180度(即R如果再增加一点,极角差就大于180度)。计算这时候区间内点的value和sum,用sum * (tot - sum)来更新答案。因为要枚举所有情况,所以要枚举所有的L。然后将L + 1,所以刚才区间的第一个点,也就是p[L]需要减掉。然后在以前值的基础之上逐渐增加R的值,不断更新sum的值,直到R和L的极角差再次刚刚小于180度,这时候更新答案。枚举完所有的L之后,就能得到答案了。

#include 

using namespace std;
typedef long long int LL;
const double PI = acos(-1.0);
const int N = 5e4 + 5;
struct Point
{
    int x, y, val;
    double rad;
};
Point p[N];
LL tot;
int n;

bool cmp(Point a, Point b) // 极角排序
{
    return a.rad < b.rad || (a.x < b.x && a.rad == b.rad);
}

double f(int a, int b) // 计算点a的极角减去点b的极角
{
    return p[a].rad < p[b].rad ? p[a].rad + 2 * PI - p[b].rad : p[a].rad - p[b].rad;
}

int main()
{
    //freopen("test.txt", "r", stdin);
    //freopen("out.txt", "w", stdout);
    int T;
    scanf("%d", &T);
    while (T--)
    {
        scanf("%d", &n);
        tot = 0;
        for (int i = 1; i <= n; i++)
        {
            scanf("%d%d%d", &p[i].x, &p[i].y, &p[i].val);
            p[i].rad = atan2(p[i].y, p[i].x) + PI;
            tot += p[i].val;
        }
        sort(p + 1, p + n + 1, cmp);
        LL ans = -1;
        LL sum = p[1].val;
        for (int L = 1, R = 1; L <= N; L++)
        {
            if (L == R)
                R = R % n + 1;
            while (R != L && f(R, L) < PI)
            {
                sum += p[R].val;
                R = R % n + 1;
            }
            R = (R - 1) % n + 1;
            ans = max(ans, sum * (tot - sum));
            sum -= p[L].val;
        }
        printf("%I64d\n", ans);
    }
    return 0;
}


你可能感兴趣的:(ACM-其它)