POJ - 2002 Squares 数正方形【二分】【哈希表】
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 数正方形【二分】【哈希表】相关推荐
- 算法动画图解:两数之和(哈希表)
更多算法动画图解,长按此链接跳转AppStore 动画 算法动画图解:两数之和(哈希表) 思路 哈希表map用来保存一个数,另一个数在遍历nums的时候和map中的数尝试求和是否为target,如果求 ...
- 【LeetCode1】两数之和_哈希表
一.题目 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标. 你可以假设每种输入只会对应一个答案.但是 ...
- 两数之和(LeetCode)——哈希表法(C语言)
上一篇文章留了个引子--用"哈希表"法来解决这个问题. 今天,我们来解决一下.为什么用哈希表法?很简单因为它--快! 讲解之前我们先来提出几个问题? 1)什么是"哈希表& ...
- LeetCode 1. 两数之和【哈希表】
1. 两数之和 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标. 你可以假设每种输入只会对应一个答案 ...
- 01两数之和(哈希表)
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标. 你可以假设每种输入只会对应一个答案.但是,数组中同一 ...
- LeetCode-1.两数之和(哈希表)
题目内容 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/two-sum 给定一个整数数组 nums 和一个整数目标值 target,请你在该数 ...
- 用js实现两数之和(哈希表)
题目: 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值target 的那两个整数,并返回它们的数组下标.你可以假设每种输入只会对应一个答案 ...
- 1.两数之和(哈希表)
[题目] 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标. 你可以假设每种输入只会对应一个答案.但是 ...
- 力扣1. 两数之和(哈希表,JavaScript)
var twoSum = function(nums, target) {let map=new Map()for(let x=0;x<nums.length;x++){if(map.has(t ...
最新文章
- linux .lz进程,LLinux常用命令(二)
- 【Linux 内核】进程管理 task_struct 结构体 ③ ( real_parent 字段 | parent 字段 | group_leader 字段 | real_cred、cred字段 )
- Python入门100题 | 第025题
- 香港小學一年級入學考試題
- duration java_Java Duration类| ofMinutes()方法与示例
- DreamWeaver连接Tomcat用以编辑和测试JSP
- 阿里云服务器排坑指南
- 单单表单独占一行_聊一聊 Excel 数据透视表的 4 种布局选项
- Python中的正则表达式找到请求体为form-data格式的请求参数
- 获取会话的连接和断开事件
- 下载网页中的html5视频之手动方法
- Plastic SCM的介绍
- KHV0031-himall3.0商城异常类(二)
- 黑客社会工程学攻击特别危险,你知道多少?
- python网络爬虫笔记-re正则表达式
- 微信小程序 share-element page-container 组件的使用
- Android平台车牌识别开发手册
- 为什么你应该学习编程?
- input和textarea中字体样式不同的解决方法
- linux下安装安装jdk和安装android studio
热门文章
- AES加密算法的详细简介
- 转:MFC 的程序中GetAt()的理解
- hprose出现500: Internal Server Error
- 使用前端框架Foundation 4来帮助简化响应式设计开发
- 2012-02-14 貌似情人节
- java byte char io流_吃透Java IO:字节流、字符流、缓冲流
- centos安装后两个启动项、_久违的更新—黑苹果的简易安装
- java 跟踪错误程序_Java异常处理 如何跟踪异常的传播路径
- 多个安卓设备投屏到电脑_辅助多手机同时直播控场 TotalControl手机投屏软件
- oracle中姓名取姓氏,Oracle SQL - 解析一個名稱字符串並將其轉換爲第一個姓氏和名字...