[算法竞赛入门经典UVa1606]两亲性分子
题目大意:
平面上有n( n ≤ 1000 n \le 1000 n≤1000)个点,每个点为白点或者黑点。现在需要放置一条隔板,使得隔板一侧的白点数加上另一侧的黑点数总数最大。隔板上的点可以看作是任意一侧。
这个题的思路是先枚举基准点A,再以基准点A为原点,算出其他点相对基准点的位置,以基准点为原点建立坐标;枚举其它点中的一个B,让基准点A和点B连成一条线作为本题的隔板,统计隔板两边黑点和白点的个数,更新答案。
解题方法:
白点黑点的处理:
把黑点旋转180°,然后统计时只统计隔板一侧的点数
判断点在隔板的一侧:首先把所有其他点按照极角增序排序,利用叉积来判断两个点之间的夹角,如果大于0那么在隔板左侧,等于0和隔板共线
极角:
利用atctan函数
c++中有两个计算极角的函数:atan(反正切函数)和atan2(四象限反正切函数)
atan的取值范围为 ( − π 2 , π 2 ) (-\frac{\pi}{2},\frac{\pi}{2}) (−2π,2π) atan2的取值范围为 ( − π , π ) (-{\pi},{\pi}) (−π,π)
atan2函数可以区分点所在的象限,更加准确
叉积:
u ⃗ × v ⃗ = ( u 1 v 2 − u 2 v 1 ) n ⃗ = ( ∣ u ⃗ ∣ ∣ v ⃗ ∣ s i n θ ) n ⃗ \vec u \times \vec v=(u_1v_2-u_2v_1) \vec n = ( \vert \vec u \vert \vert \vec v \vert sin \theta ) \vec n u×v=(u1v2−u2v1)n=(∣u∣∣v∣sinθ)n
( 1 ) 当 u 1 v 2 − u 2 v 1 > 0 , s i n θ > 0 , θ ∈ ( 0 , π ) , v ⃗ 相 对 n ⃗ 逆 时 针 旋 转 π 以 内 (1)当u_1v_2-u_2v_1 > 0,sin\theta >0 ,\theta \in (0,\pi) , \vec v相对 \vec n逆时针旋转 \pi以内 (1)当u1v2−u2v1>0,sinθ>0,θ∈(0,π),v相对n逆时针旋转π以内
( 2 ) 当 u 1 v 2 − u 2 v 1 < 0 , s i n θ < 0 , θ ∈ ( π , 2 π ) , v ⃗ 相 对 n ⃗ 逆 时 针 旋 转 π 以 上 (2)当u_1v_2-u_2v_1 < 0,sin\theta <0 ,\theta \in (\pi,2\pi) , \vec v相对 \vec n逆时针旋转 \pi以上 (2)当u1v2−u2v1<0,sinθ<0,θ∈(π,2π),v相对n逆时针旋转π以上
( 3 ) u 1 v 2 − u 2 v 1 = 0 , s i n θ = 0 , v ⃗ 与 n ⃗ 共 线 (3)u_1v_2-u_2v_1 = 0,sin\theta = 0 , \vec v 与 \vec n共线 (3)u1v2−u2v1=0,sinθ=0,v与n共线
扫描时计算L和R的叉积如果 ≥ 0 \ge0 ≥0,那么新点R在隔板L的左侧,符合要求
#include<cmath>
#include<cstdio>
#include<algorithm>
#define rep(i,a,b) for(int i=(a); i<(int)(b);++i)
const int maxn=1005;
int n,color[maxn];
struct node
{
int x,y;
double rad;
bool operator < (const node& rhs)const
{
return rad<rhs.rad;
}
}op[maxn],p[maxn];
bool left(node& A,node& B)//叉积
{
return A.x*B.y-A.y*B.x>=0;
}
int infer()
{
if(n<=2)
return 2;
int ans=0;
rep(i,0,n)
{
int k=0;
rep(j,0,n)
if(i!=j)
{
p[k].x=op[j].x-op[i].x;
p[k].y=op[j].y-op[i].y;
if(color[j])//把黑点坐标旋转180,并且统计时只统计一边
{
p[k].x=-p[k].x;
p[k].y=-p[k].y;
}
p[k].rad=atan2(p[k].y,p[k].x);
++k;
}
std::sort(p,p+k);
int L=0,R=0,cnt=2;//开始有两个点,一个是i,也就是算完相对位置后的原点,一个是L
while(L<k)//把隔板L转圈,一直转完所有的点
{
if(L==R)//???
{
R=(R+1)%k;//R为[0,k)
++cnt;
}
while(R!=L&&left(p[L],p[R]))//R是L左边的点,右手定则
{
R=(R+1)%k;
++cnt;
}
--cnt;//舍去多计入的点,也可以理解为由于分隔线的旋转,原来在分隔线上的点现在变为了右侧的点,要减掉一个
++L;
ans=std::max(ans,cnt);
}
}
return ans;
}
int main()
{
while(scanf("%d",&n)==1&&n)
{
rep(i,0,n)
scanf("%d%d%d",&op[i].x,&op[i].y,&color[i]);
printf("%d\n",infer());
}
return 0;
}
参考: