UVA - 1606 Amphiphilic Carbon Molecules 【极角排序+扫描法】

题目链接:https://cn.vjudge.net/problem/UVA-1606

题意:

直角坐标上有若干个白点和黑点,用一块隔板隔开这些点,一边只计算白点个数另一半只计算黑点个数,如果正好位于隔板上的点,不论黑白都计算,问能计算的点数最多有几个;

思路:(算是做过比较痛苦的题了吧,全程就题目读懂了,思路都是看别人的)

先讲一些基础内容:

极角排序:极角排序是根据角度大小顺序排序的过程;方法主要有两种;

第一种是用 atan2()函数计算出极角,根据大小排序,这种方法在处理相同角度的两个点,可能算出来的极角会不相同,因为精度上比较容易出现误差,但是速度很快;(atan2()是atan()的升级版,两者不同在于带入数据的时候,前者是y在前,x在 后,后者相反,计算结果前者比后者稳定,具体的可以百度了解一下)

第二种方法是利用向量叉积来判断(一开始我以为能够算出对应的极角值然后排序,然而只是判断两个点的极角谁大谁小罢了,但是也是能够完成排序的,但是求极角值可能就不是特别好用)注意:下面的字母统一表示向量;

a×b = |a|*|b| sin \Theta,向量叉积的公式应该没忘吧; a×b 还有另外一种求法,这里我直接讲结论,大家可以翻一下高数的课本,应该是有相关内容的,我的课本就有; a(x1,y1),b (x2, y2 )  ,则 a×b = x1*y2 - y1*x2 ,那么就有 x1*y2 - y1*x2  =  |a|*|b| sin \Theta

因为|a|和|b| 一定是为正数的,所以a*b的值得正负只能由sin\Theta决定,sin\Theta是正的,则说明a和b向量的夹角是小于或者等于180度的,反之则是大于180度;那这个结论有什么用呢?我们在用sort排序的时候,是不是会写一个bool cmp的函数,返回值如果是1,就不用交换两个要排序的数,如果是0,则交换两个数;同样的,我们可以根据两个要极角排序点对应的sin \Theta的正负来排序;

基础内容讲完;接下来就是如果去解这道题了;

我们想到最直接的方法就是取任意两个点,然后求出对应的直线方程,然后遍历所有的点,看看是在这个直线的上方还是下方,然后算出对应的点的数量;任意取两点的时间复杂度是 n*n,每确定一条直线就遍历n-1个点,算出满足条件的点的数量,那么全程时间复杂度就是n*n*n,n最大值是1000,显然是超时的;


我们先来看看首先要解决的小问题:坐标上的点用一个隔板隔开,那么隔板的两边都有我们不需要计算的点,因为这个因素,我们计算点的时候会面临两边应该如何划分黑白区域才能是最优解的问题;解决这个问题的方法有两个,第一是假设两次,我假设这一边是白色区域,另一边是黑色区域,计算出对应的点数后,再把先前假设为白色的区域假设为黑色,把先前黑色的区域假设为白色,然后再计算一次,然后取两次假设点数最大的那个,这样就要多一个常数的时间复杂度;另一种方法就比较巧妙:我假设一边为白色区域,那白色区域这部分的黑色点是我不要的点,我把这些不要的黑色点丢到另一边,然后把另一边我需要计算的黑色的点拿过来;那么我们要计算的点就都集中在隔板的一边了;我们通过中心对称的原则,把对应的黑点坐标倒过来就能做到从另一边拿黑点的操作;

如果我们用上面说的第二种方法处理过后,我们要计算的点就总是位于隔板的一边,那么计算点数就变得简单了;每枚举一个基点,就能得到n-1个极角,这些极角,可以当作是n-1种不一样的隔板,所以我们需要枚举这n-1种隔板,求出能得到的最大点数是多少;我们先给这几个隔板按照极角大小排好序,枚举这n-1种隔板的时候,就是以逆时针方向枚举;因为我们只需要计算180度内(也就是隔板的一边)有多少个点就行,所以我们需要一个左界和右界,根据左界和右界的角度来保证当前计算的点都是180度内的点;假设枚举隔板(对应的极角)为左界,右界从左界开始依次扫描,直到左界和右界大于180度就停止扫描,然后开始枚举下一个隔板,也就是下一个极角,但这个时候,右界并不需要从左界开始重新扫描,左界+1(枚举下一个极角)就说明当前左界和右界内的点数少了一个点(左界和右界都是穿过一个点的,也就相当于代表了一个点),右界不需要从左界开始重新扫描,只需要继续逆时针扫描直到左界和右界大于180度,左界再+1就行了,每完成一个180的扫描就记录下当前的点数; 当左界枚举完最后一个极角,那么就结束扫描,再换另一个基点;

下面是代码:(注意叉积排序的cmp中,a1到a2为正,所以左界在前,右界在后,如果反过来的话,符号就是负了)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<queue>
#include<map>
#include<stack>
#include<sstream>
#include<vector>

using namespace std;

#define IOS ios::sync_with_stdio(false); cin.tie(0);

int read(){
    int r=0,f=1;char p=getchar();
    while(p>'9'||p<'0'){if(p=='-')f=-1;p=getchar();}
    while(p>='0'&&p<='9'){r=r*10+p-48;p=getchar();}return r*f;
}

typedef long long ll;
const int Maxn = 1010;
const long long LINF = 1e18;
const int INF = 0x3f3f3f3f;

struct Node {
    int x,y,col;
    double val;
    bool operator < (const Node & a1) const {
        return val < a1.val;
    }
} a[Maxn],ang[Maxn];

bool cmp (const Node& a1, const Node& a2) { // 用叉积判断两点的角度是否大于180度,正为小于或者等于180度
    return (a1.x*a2.y-a1.y*a2.x) >= 0;
}

int main (void)
{
    int n,ans,M;
    while (scanf("%d",&n) != EOF) {
        if(!n) break;
        ans = 0;
        for (int i = 1; i <= n; ++i) {
                scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].col);
        }
        if(n <= 3) { printf("%d\n",n); continue; }
        for (int i = 1; i <= n; ++i) {
                M = 0;
            for (int j = 1; j <= n; ++j) {
                if(i == j) continue; 
                ang[M].x = a[j].x-a[i].x; ang[M].y = a[j].y-a[i].y;
                if(a[j].col) {    // 原点对称
                    ang[M].x*=(-1); ang[M].y*=(-1);
                } 
                ang[M].val = atan2(ang[M].y,ang[M].x);  // 反三角函数
                M++;
            }
            sort(ang,ang+M);  // 根据反三角函数的值从小到大排序
            int ri = 0,le = 0,tmp = 2;
            while (le < M) {
                if(ri == le) {  // 这个判断语句不仅是为了初始,当ri转完一圈与le相遇的时候,le会不断的+1,
                    ri = (ri+1)%M; tmp++;  // 直到le和ri重新成180度角的时候,就继续计算tmp的值;
                }
                while (ri != le && cmp(ang[le],ang[ri])) {  // 如果不加ri != le,个别数据会出现死循环;比如样例2就会
                    ri = (ri+1)%M; tmp++;
                }
                le++; tmp--;
                ans = max(ans,tmp);
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/godleaf/article/details/81808719