首先如果我们知道了一条直线左边和右边的点,只需要将左边点的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 <bits/stdc++.h>
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;
}
本文介绍一种基于极角排序的算法,用于解决通过枚举切割点来找到最优直线切割的问题。通过尺取法计算特定条件下两组点集value求和的乘积的最大值。
1140

被折叠的 条评论
为什么被折叠?



