题意:
给你几个点的坐标,每个点有一个value值,每两个点之间有一条线段,这条线段的value值是线段的两个端点的value值乘积,题目保证没有过原点的线段,
让你找一条过原点的直线,满足和直线相交的线段的value值之和最大,输出最大和。
思路:
如果画草图的时候画了几个点并且把点两两相连,那么你是很难找到规律的。
应该这样考虑,先画几个点,不连线,然后做一条过原点的直线,你会发现这些点分布在直线的两侧,如果直线和线段相交的话,一定是直线两端任取一点相连得到的线段。
假如直线一侧有a,b,c三个点,另一侧有d,e两个点,那么题目得到的最终结果是a*d+a*e+b*d+b*e+c*d+c*e;
把上面的式子用乘法分配律变形:a*(d+e)+b*(d+e)+c*(d+e);再一次变形:(a+b+c)*(d+e);发现了什么!是一侧点上的value值之和乘以另一侧上的点的value值之和。
那么,接下来,就应该以过原点的直线为分割线来考虑怎么优雅地暴力解决这个问题。
肯定是要对斜率进行排序的。
分析一下笛卡尔二维坐标轴,斜率最小的那个点应该位于第二(或四)象限且类似于(-1,1e9)(或(1,-1e9))这样极端的点,斜率最大的那个点应该位于第一(或三)象限且类似于(1,1e9)(或(-1,-1e9))这样的点。
那么我们以什么为初始的直线呢?考虑到我们暴力时如果能记下初始直线左右两边的value值之和,那么从数组的0到n-1遍历的过程相当于直线旋转的过程,每次根据点的位置判断这个点是归到直线的哪一侧。因为是从下标0开始遍历,而排序又是斜率k从小到大,最小的那个斜率无论二四象限都是接近于Y轴的,所以我们以Y轴为初始直线。因为我们这条直线是靠斜率来构造出来的,在代码里其实并没有体现出这条直线的具体位置(毕竟直线不唯一),初始的时候应该注意一下Y轴负方向上的点应该归到左侧(具体看代码)。
然后中间遍历过程相当于直线从Y轴开始逆时针旋转了180度,旋转过程中每一个点应该加到哪一边这里仔细想一想应该能想明白,文字不好描述就不再赘述了。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
struct Point
{
int x,y,v;
double k;
}a[50050];
bool cmp(Point A,Point B)
{
return A.k<B.k;
}
int main()
{
int n,t;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].v);
a[i].k=(double)a[i].y/(double)a[i].x;
}
sort(a,a+n,cmp);
LL sumleft=0,sumright=0,ans=0;
for(int i=0;i<n;i++)
{
if(a[i].x<0||(a[i].x==0&&a[i].y<0))sumleft+=a[i].v;
else sumright+=a[i].v;
}
for(int i=0;i<n;i++)
{
if(a[i].x<0||(a[i].x==0&&a[i].y<0))
{
sumleft-=a[i].v;
sumright+=a[i].v;
}
else
{
sumleft+=a[i].v;
sumright-=a[i].v;
}
ans=max(ans,sumleft*sumright);
}
printf("%lld\n",ans);
}
return 0;
}