Squares

POJ - 2002

题意

平面上有N个点,任取4个点,求能组成正方形的不同组合方式有多少种;相同的四个点,不同顺序构成的正方形视为同一正方形

分析

做法1

先枚举两个点,通过计算得到另外两个点,然后检查点集中是否存在这两个点

(tx,ty)绕坐标原点旋转W(弧度制)之后点的坐标为(x,y)

    double x = tx * cos(w) - ty * sin(w);double y = tx * sin(w) + ty * cos(w);

假如枚举出的两个点分别是A,B,那么C点就是B围绕A旋转90度,D就是A围绕B旋转-90度,或者C点就是B围绕A旋转-90度,D就是A围绕B旋转90度

因为一条边可能对应两个方向不同的正方形嘛,那么怎么使枚举的时候不会重复计算正方形呢

假如枚举A是用下标i,那么枚举B的时候j要从i+1开始枚举

另外,在二分查找点的时候,应该在排序后的[j+1,n]的下标范围内二分查找,也就是查找B点后面的点,这样点的枚举就有次序了,就不会出现重复记数了

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<queue>
#include<cstdio>
#include<string>
#include<math.h>
#include<algorithm>
#include<map>
#include<set>
#include<stack>
#define mod 500500507
#define INF 0x3f3f3f3f
#define eps 1e-5
#define  LL long long
using namespace std;
const int maxn=1000+10;
struct Point{int x,y;Point(){}Point(int a,int b){x=a;y=b;}bool operator < (const Point &b){if(x==b.x)return y<b.y;return x<b.x;}
}p[maxn];bool BS(Point po,int l,int r){int left=l,right=r,mid;while(left<=right){int mid=(left+right)>>1;if(p[mid].x==po.x&&p[mid].y==po.y)return true;if(p[mid]<po)left=mid+1;elseright=mid-1;}return false;
}void transXY(Point a,Point b,Point &c,int f){int tx=b.x-a.x,ty=b.y-a.y;c.x=a.x-ty*f;c.y=a.y+tx*f;
}int main(){int n;while(scanf("%d",&n)!=EOF&&n){for(int i=1;i<=n;i++){scanf("%d%d",&p[i].x,&p[i].y);}sort(p+1,p+1+n);int ans=0;for(int i=1;i<=n-3;i++){for(int j=i+1;j<=n-2;j++){Point c,d;transXY(p[i],p[j],c,1);transXY(p[j],p[i],d,-1);if(BS(c,j+1,n)&&BS(d,j+1,n))ans++;transXY(p[i],p[j],c,-1);transXY(p[j],p[i],d,1);if(BS(c,j+1,n)&&BS(d,j+1,n))ans++;}}printf("%d\n",ans);}
}

做法2

先枚举两个点,通过数学公式得到另外两个点,使得这四个点能够成正方形。然后检查点集中是否存在计算出来的那两个点,若存在,说明有一个正方形

但是这种做法会使同一个正方形被枚举4次,因此最后结果要除以4

已知:正方形的两个点 (x1,y1)  (x2,y2)

则:正方形另外两个点的坐标为  

 x3=x1+(y1-y2)   y3= y1-(x1-x2)

x4=x2+(y1-y2)   y4= y2-(x1-x2)

x3=x1-(y1-y2)   y3= y1+(x1-x2)

x4=x2-(y1-y2)   y4= y2+(x1-x2)

利用hash[]标记散点集

我个人推荐key值使用 平方求余法

即标记点x y时,key = (x^2+y^2)%prime

此时key值的范围为[0, prime-1]

由于我个人的标记需求,我把公式更改为key = (x^2+y^2)%prime+1

使得key取值范围为[1, prime],则hash[]大小为 hash[prime]

其中prime为 小于 最大区域长度(就是散点个数)n的k倍的最大素数,

即小于k*n 的最大素数 (k∈N*)

为了尽量达到key与地址的一一映射,k值至少为1,

当为k==1时,空间利用率最高,但地址冲突也相对较多,由于经常要为解决冲突开放寻址,使得寻找key值耗时O(1)的情况较少

当n太大时,空间利用率很低,但由于key分布很离散,地址冲突也相对较少,使得寻找键值耗时基本为O(1)的情况

提供一组不同k值的测试数据

K==1,   prime=997    1704ms

K==2,   prime=1999   1438ms

K==8,   prime=7993   1110ms

K==10,  prime=9973   1063ms

K==30,  prime=29989  1000ms

K==50,  prime=49999  1016ms

K==100, prime=99991  1000ms

最后解决的地址冲突的方法,这是hash的难点。我使用了 链地址法

typedef class HashTable{public:int x,y;   //标记key值对应的x,yHashTable* next;  //当出现地址冲突时,开放寻址HashTable()  //Initial{next=0;}}Hashtable;Hashtable* hash[prime];   //注意hash[]是指针数组,存放地址//hash[]初始化为NULL (C++初始化为0)

先解释所谓的“冲突”

本题对于一组(x,y),通过一个函数hash(x,y),其实就是上面提到的key的计算公式

key = (x^2+y^2)%prime+1

于是我们得到了一个关于x,y的key值,但是我们不能保证key与每一组的(x,y)都一一对应,即可能存在 hash(x1,y1) = hash(x2,y2) = key

处理方法:

(1) 当读入(x1, y1)时,若hash[key]为NULL,我们直接申请一个临时结点Hashtable* temp,记录x1,y1的信息,然后把结点temp的地址存放到hash[key]中

此后我们就可以利用key访问temp的地址,继而得到x1,y1的信息

(2) 当读入(x2, y2)时,由于hash(x1,y1) = hash(x2,y2) = key,即(x2, y2)的信息同样要存入hash[key],但hash[key]已存有一个地址,怎么办?

注意到hash[key]所存放的temp中还有一个成员next,且next==0,由此,我们可以申请一个新结点存放x2,y2的信息,用next指向这个结点

此后我们利用key访问temp的地址时,先检查temp->x和temp->y是否为我们所需求的信息,若不是,检查next是否非空,若next非空,则检查下一结点,直至 next==0

当检查完所有next后仍然找不到所要的信息,说明信息原本就不存在

就是说hash[key]只保存第一个值为key的结点的地址,以后若出现相同key值的结点,则用前一个结点的next保存新结点的地址,其实就是一个链表

简单的图示为:

//Memory Time
//652K  1438MS #include<iostream>
using namespace std;const int prime=1999;  //长度为2n区间的最大素数 (本题n=1000)//其他prime可取值:
// 1n  区间: 997   1704ms
// 2n  区间: 1999  1438ms
// 8n  区间: 7993  1110ms
// 10n 区间: 9973  1063ms
// 30n 区间: 29989 1000ms
// 50n 区间: 49999 1016ms
// 100n区间: 99991 1000ms//为了尽量达到key与地址的一一映射,hash[]至少为1n,
//当为1n时,空间利用率最高,但地址冲突也相对较多,由于经常要为解决冲突开放寻址,使得寻找key值耗时O(1)的情况较少
//当n太大时,空间利用率很低,但由于key分布很离散,地址冲突也相对较少,使得寻找键值耗时基本为O(1)的情况typedef class
{public:int x,y;
}Node;typedef class HashTable
{public:int x,y;   //标记key值对应的x,yHashTable* next;  //当出现地址冲突时,开放寻址HashTable()  //Initial{next=0;}
}Hashtable;Node pos[1001];
Hashtable* hash[prime];   //hash[]是指针数组,存放地址void insert_vist(int k)
{int key=((pos[k].x * pos[k].x)+(pos[k].y * pos[k].y))%prime +1;   //+1是避免==0//使key从[0~1998]后移到[1~1999]if(!hash[key]){Hashtable* temp=new Hashtable;temp->x=pos[k].x;temp->y=pos[k].y;hash[key]=temp;}else   //hash[key]已存地址,地址冲突{Hashtable* temp=hash[key];while(temp->next)     //开放寻址,直至next为空temp=temp->next;temp->next=new HashTable;   //申请新结点,用next指向,记录x、ytemp->next->x=pos[k].x;temp->next->y=pos[k].y;}return;
}bool find(int x,int y)
{int key=((x * x)+(y * y))%prime +1;if(!hash[key])   //key对应的地址不存在return false;else{Hashtable* temp=hash[key];while(temp){if(temp->x==x && temp->y==y)return true;temp=temp->next;}}return false;
}int main(void)
{int n;while(cin>>n){if(!n)break;memset(hash,0,sizeof(hash));   //0 <-> NULLfor(int k=1;k<=n;k++){cin>>pos[k].x>>pos[k].y;insert_vist(k);   //插入哈希表,标记散点}int num=0;  //正方形的个数for(int i=1;i<=n-1;i++)for(int j=i+1;j<=n;j++){int a=pos[j].x-pos[i].x;int b=pos[j].y-pos[i].y;int x3=pos[i].x+b;int y3=pos[i].y-a;int x4=pos[j].x+b;int y4=pos[j].y-a;if(find(x3,y3) && find(x4,y4))num++;x3=pos[i].x-b;y3=pos[i].y+a;x4=pos[j].x-b;y4=pos[j].y+a;if(find(x3,y3) && find(x4,y4))num++;}cout<<num/4<<endl;  //同一个正方形枚举了4次}return 0;
}

POJ - 2002 Squares 数正方形【二分】【哈希表】相关推荐

  1. 算法动画图解:两数之和(哈希表)

    更多算法动画图解,长按此链接跳转AppStore 动画 算法动画图解:两数之和(哈希表) 思路 哈希表map用来保存一个数,另一个数在遍历nums的时候和map中的数尝试求和是否为target,如果求 ...

  2. 【LeetCode1】两数之和_哈希表

    一.题目 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标. 你可以假设每种输入只会对应一个答案.但是 ...

  3. 两数之和(LeetCode)——哈希表法(C语言)

    上一篇文章留了个引子--用"哈希表"法来解决这个问题. 今天,我们来解决一下.为什么用哈希表法?很简单因为它--快! 讲解之前我们先来提出几个问题? 1)什么是"哈希表& ...

  4. LeetCode 1. 两数之和【哈希表】

    1. 两数之和 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标. 你可以假设每种输入只会对应一个答案 ...

  5. 01两数之和(哈希表)

    给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标. 你可以假设每种输入只会对应一个答案.但是,数组中同一 ...

  6. LeetCode-1.两数之和(哈希表)

    题目内容 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/two-sum 给定一个整数数组 nums 和一个整数目标值 target,请你在该数 ...

  7. 用js实现两数之和(哈希表)

    题目:         给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值target  的那两个整数,并返回它们的数组下标.你可以假设每种输入只会对应一个答案 ...

  8. 1.两数之和(哈希表)

    [题目] 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标. 你可以假设每种输入只会对应一个答案.但是 ...

  9. 力扣1. 两数之和(哈希表,JavaScript)

    var twoSum = function(nums, target) {let map=new Map()for(let x=0;x<nums.length;x++){if(map.has(t ...

最新文章

  1. linux .lz进程,LLinux常用命令(二)
  2. 【Linux 内核】进程管理 task_struct 结构体 ③ ( real_parent 字段 | parent 字段 | group_leader 字段 | real_cred、cred字段 )
  3. Python入门100题 | 第025题
  4. 香港小學一年級入學考試題
  5. duration java_Java Duration类| ofMinutes()方法与示例
  6. DreamWeaver连接Tomcat用以编辑和测试JSP
  7. 阿里云服务器排坑指南
  8. 单单表单独占一行_聊一聊 Excel 数据透视表的 4 种布局选项
  9. Python中的正则表达式找到请求体为form-data格式的请求参数
  10. 获取会话的连接和断开事件
  11. 下载网页中的html5视频之手动方法
  12. Plastic SCM的介绍
  13. KHV0031-himall3.0商城异常类(二)
  14. 黑客社会工程学攻击特别危险,你知道多少?
  15. python网络爬虫笔记-re正则表达式
  16. 微信小程序 share-element page-container 组件的使用
  17. Android平台车牌识别开发手册
  18. 为什么你应该学习编程?
  19. input和textarea中字体样式不同的解决方法
  20. linux下安装安装jdk和安装android studio

热门文章

  1. AES加密算法的详细简介
  2. 转:MFC 的程序中GetAt()的理解
  3. hprose出现500: Internal Server Error
  4. 使用前端框架Foundation 4来帮助简化响应式设计开发
  5. 2012-02-14 貌似情人节
  6. java byte char io流_吃透Java IO:字节流、字符流、缓冲流
  7. centos安装后两个启动项、_久违的更新—黑苹果的简易安装
  8. java 跟踪错误程序_Java异常处理 如何跟踪异常的传播路径
  9. 多个安卓设备投屏到电脑_辅助多手机同时直播控场 TotalControl手机投屏软件
  10. oracle中姓名取姓氏,Oracle SQL - 解析一個名稱字符串並將其轉換爲第一個姓氏和名字...