广场舞 (第七届蓝桥杯决赛题过50%数据讲解代码+过100%数据思路)

广场舞

LQ市的市民广场是一个多边形,广场上铺满了大理石的地板砖。

地板砖铺得方方正正,就像坐标轴纸一样。
以某四块砖相接的点为原点,地板砖的两条边为两个正方向,一块砖的边长为横纵坐标的单位长度,则所有横纵坐标都为整数的点都是四块砖的交点(如果在广场内)。

广场的砖单调无趣,却给跳广场舞的市民们提供了绝佳的参照物。每天傍晚,都会有大批市民前来跳舞。
舞者每次都会选一块完整的砖来跳舞,两个人不会选择同一块砖,如果一块砖在广场边上导致缺角或者边不完整,则没人会选这块砖。

(广场形状的例子参考【图1.png】)



现在,告诉你广场的形状,请帮LQ市的市长计算一下,同一时刻最多有多少市民可以在广场跳舞。



【输入格式】
输入的第一行包含一个整数n,表示广场是n边形的(因此有n个顶点)。
接下来n行,每行两个整数,依次表示n边形每个顶点的坐标(也就是说广场边缘拐弯的地方都在砖的顶角上。数据保证广场是一个简单多边形。

【输出格式】
输出一个整数,表示最多有多少市民可以在广场跳舞。

【样例输入】
5
3 3
6 4
4 1
1 -1
0 4

【样例输出】
7

【样例说明】
广场如图1.png所示,一共有7块完整的地板砖,因此最多能有7位市民一起跳舞。

【数据规模与约定】
对于30%的数据,n不超过100,横纵坐标的绝对值均不超过100。
对于50%的数据,n不超过1000,横纵坐标的绝对值均不超过1000。
对于100%的数据,n不超过1000,横纵坐标的绝对值均不超过100000000(一亿)。


资源约定:
峰值内存消耗 < 256M
CPU消耗  < 1000ms

请严格按要求输出,不要画蛇添足地打印类似:“请您输入...” 的多余内容。

所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。

注意: main函数需要返回0
注意: 只使用ANSI C/ANSI C++ 标准,不要调用依赖于编译环境或操作系统的特殊函数。
注意: 所有依赖的函数必须明确地在源文件中 #include <xxx>, 不能通过工程设置而省略常用头文件。

提交时,注意选择所期望的编译器类型。

过50%数据思路(整道题的一般思路):

首先这是一道计算几何的很好的题目,题目要求所给的多边形(任意多边形)内部有多少个完整的小正方形。

我们的思路可以是这样的,首先我们根据所给的所有顶点确定这个图形的上下左右最大范围,即横坐标取出最大最小值,纵坐标取出最大最小值我们就得到了一个大体的矩形范围,然后在这个矩形范围内枚举每个点

枚举每个点干什么呢?题目不是要我们求出有多少的小正方形吗,我们可以这样,枚举每个点,然后以这个点为一个角,我就以右上角为例了,只需要判断这个点以及这个点的左,下,左下点都在所给的多边形范围内,是不是就得到了一个多边形范围内的一个完整正方形了,答案就加一(当然你以这个点为哪个角都无所谓,因为最终要求四个点都在多边形内),那么我们做这道题的主要问题是不是就转化成了,判断一个点是否在多边形内啦?


好的下面介绍一个判断法:

水平/垂直交叉点数判别法(适用于任意多边形)

注意到如果从P作水平向左(右)的射线的话,如果P在多边形内部,那么这条射线与多边形的交点必为奇数,如果P在多边形外部,则交点个数必为偶数(0也在内)。所以,我们可以顺序考虑多边形的每条边(依次枚举),求出交点的总个数。还有一些特殊情况要考虑。

首先我们先看最一般的情况,要测试的点(以下称测试点)

一:测试点恰好在端点上,那么说明一定相当于在图形内部,这种情况也是最好判断的情况,直接枚举所有顶点,看横纵坐标是否相等

二:测试点恰好在边上,其实第一种情况是第二种的一种特例,我们就不在考虑再点上的情况了,直接考虑边上不包括在顶点的情况。枚举每条边(题目按顺序给出顶点,所以相邻顶点构成一条边)分两种:

      

      1:图一所示枚举的这条边有斜率的情况下,我们先求出这条边的斜率,在求出测试点和一个顶点的斜率,如果斜率相同就有图一三种情况,在线段上一种和在线段延长线上两种,只有在线段上才是在多边形内,所以斜率相同并且t的x横坐标在两个端点x横坐标之间的时候是在形内的。

       2:图二所示枚举的边斜率无穷大,就是三个点x值相同时,说明斜率无穷大,然后只需让t的y值在两个端点的y值之间就可保证在形内


三:就是测试点不在多边形上,那么我们从测试点向左引一条射线,和多边形交点个数为奇数个(通常1个)在形内,偶数个(通常0个或2个)在形外,但是我们不可能一下子求出交点个数和,方法就是枚举每条边,求和每条边的交点和,但实际我们也不用真求和,没多一个交点奇偶性就会变一下,所以每多一个点(和每条边至多1个交点)奇偶性就变一下就好了,每次只用0和1代替就好,最后直接返回奇偶性即可,1就是形内,0就在形外。怎么判断呢?

这是需要用到直线方程的两点式方程 因为我们每次枚举边就是枚举两个点嘛,所以用两点式方程更方便,用它干什么呢?

我么把测试点的yt值代入直线方程,是不是就求出了直线上纵坐标为yt的横坐标xt'

如图:


那么如果这个测试点在点AB之间,并且xt’> xt那么就有了一个交点,奇偶性就变一下。

相信大家已经发现了我上面说的是在AB之间的情况(一般情况)那么下面看下面几个图的特殊情况:


发现了没有特殊情况就是恰好交在交点上,这种情况按照我们的规定,奇数在内部,对于图中形外的AB两点就不满足了。因此我们有一下规则处理这种特殊情况:

对于一条边(p1,p2)来说,如果测试点的y值和max(yp1,yp2)相等的话就忽略这种情况,其他正常计算,大家可以利用这个规则对比上图的几个点算一算是不是就对啦。

也就说总的来说,两个端点,一个端点的y大于测试点的y另一个小于等于测试点的y这种情况下,然后满足求出xt’ > xt这种情况下就是有交点啦,奇偶性变化,其他全都忽略不变。

那么一个端点的y大于测试点的y另一个小于等于测试点的y这个判断语句怎么写呢,当然笨最易理解的就是分类讨论了,但是下面有个高逼格的写法:

(vy[i] > testy) != (vy[j] > testy),也就是一个端点的y大于测试点的y(值为1)一个端点的y小于等于测试点的y(值为0)这样就满足的!=,这样就是真1,判断就完成了,不用麻烦的分类了。

上代码:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
//待输入点的坐标
double vx[100],vy[100];
int num;//顶点个数

//关键函数,判断点是否在凸多边形内
int isIn(double testx,double testy){
    for(int k = 0; k < num; k++){
        if(testx == vx[k] && testy == vy[k])//这个点恰好是顶点,当然算在里面
            return 1;
    }

    for(int i = 0; i < num; i++){
        double dy = vy[(i+1)%num] - vy[i];
        double dx = vx[(i+1)%num] - vx[i];
        if(dx == 0.0) continue;
        double k = dy / dx;//算出多边形某条面的斜率
        if((vy[i] - testy) / (vx[i]-testx) == k && (testx > vx[i]) != (testx > vx[(i+1)%num]))
            return 1;
        //上面的判断什么意思呢?我们来分析一下,首先发现这个测试点和边上一点的斜率和这条边斜率相同说明什么
        //这保证了这个点必定在这条线段上或者线段的延长线上,那么只有当在线段上才是在图形内部
        //所以只需要让测试点的x横坐标在线段连端点的x横坐标之间即可,上面写法的意思就是不能在同侧
        //测试点x值不能同时在两端点左侧,也不能同时在两端右侧,而在某一个点上的情况之前就已经判断了
    }

    //上面for循环判断了在边上的情况,我们用到了斜率,但是有一种斜率是求不出来的,就是和y轴平行的时候下面就是对这种情况的判断
    for(int i = 0; i < num; i++){
        if(testx == vx[i] && vx[i] == vx[(i+1)%num] && (testy > vy[i]) != (testy > vy[(i+1)%num]))
            return 1;
    //三点x横坐标相等,测试点的y值在两端点之间
    }

    //以上点在边上的情况就判断完了,下面不在边上的情况我们就利用水平射线相交点判别法判断
    int flag = 0;//记录交点个数的奇偶性
    for(int i = 0,j = num-1; i < num; j = i++){
    if((vy[i] > testy) != (vy[j] > testy) && (testx < (vx[j] - vx[i]) * (testy - vy[i]) / (vy[j] - vy[i]) + vx[i]))
            flag = !flag;
    }
    return flag;
    //具体解释见上面博客的解释
}

bool IsSquare(double x,double y){
    //判断是否可以构成小正方形及判断它本身和它的左,下,左下点是否在凸多边形内
    if(isIn(x,y)){
        if(!isIn(x+1.0,y)) return false;
        if(!isIn(x,y-1.0)) return false;
        if(!isIn(x+1.0,y-1.0)) return false;
        return true;
    }
    else
        return false;
}

int main(){
    int ans = 0;
    scanf("%d",&num);
    for(int i = 0; i < num; i++){
        scanf("%lf%lf",&vx[i],&vy[i]);
    }
    //获得图形的边界
    double xmax = vx[0],xmin = vx[0];
    double ymax = vy[0],ymin = vy[0];
    for(int i = 0; i < num; i++){
        xmax = max(xmax,vx[i]);
        xmin = min(xmin,vx[i]);
        ymax = max(ymax,vy[i]);
        ymin = min(ymin,vy[i]);

    }

    //扫描范围内的每个点
    for(double x = xmin; x <= xmax; x++){
        for(double y = ymax; y >= ymin; y--){
            //判断是否可以构成一个完整的小正方
            if(IsSquare(x,y)) ans++;
        }
    }
    printf("%d\n",ans);
    return 0;
}

过100%数据思路:

根据所给的样例范围我们发现因为要枚举我找到的矩形范围内的所有点,所以对于50%的数据应该是没有问题的,但是100%数据是一亿,肯定不能n^2枚举,我想到的思路是利用二维坐标离散化,并且离散的过程中要记录下离散了多少,这样可以还原回去,那么搜索的时候,就按离散化的搜,结束后再还原出原来的比例,得到原本的结果。这个相法是来源于

HDU5925:Coconuts,这道题的做法就是先离散化,记录下离散的量,最后在还原,不过我不会,也没看懂。感觉这个思路应该是可行的,望大家指正,有大佬可以帮忙实现一下大笑


展开阅读全文

没有更多推荐了,返回首页