涉及知识点:
1.向量叉积及求凸包
文章目录
A - Beauty Contest
题目链接:POJ2187
题目大意:给定平面中N个点,求最远点对
分析
朴素思路:O( N 2 N^2 N2)枚举
优化:
①最远点对必定在凸包上
②暴力枚举凸包上的点,找最远距离
因为凸包上的点个数小于等于N,所以减少了时间。
上面的思路已经可以过题了(因为数据弱),但是细想一下,如果它给出的数据极端一点,所有点都在凸包上,那仍然会T;也就是说这种做法并不能百分百缩小枚举的范围
更优解法:
最远点对必定是对踵点,而对踵点对数不超过
3
2
N
\frac{3}{2}N
23N(网上看到的,并没有证明过)
所以如果可以枚举所有对踵点,就可以
O
(
N
)
O(N)
O(N)解出
对踵点可以利用旋转卡壳O(N)求得
旋转卡壳的思想可以参考这篇某个巨佬写的文章:旋转卡壳,非常通俗易懂。
但是貌似有一点点小瑕疵,我做了一些修改。
凸包+暴力代码
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
const double eps=1e-8;
typedef struct{
int x,y;
}Point;
typedef Point Vector;
Point P[50005];
Point Ch[50005];
Vector operator - (Point a,Point b){
return Vector {a.x-b.x,a.y-b.y};
}
int Cross(Vector a,Vector b){
return a.x*b.y-a.y*b.x;
}
bool cmp(Point a,Point b){
if(a.x!=b.x) return a.x<b.x;
return a.y<b.y;
}
int dis(Point a,Point b){
return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y);
}
int main()
{
int N;
scanf("%d",&N);
for(int i=0;i<N;i++)
scanf("%d%d",&P[i].x,&P[i].y);
sort(P,P+N,cmp);
int cnt=0;
for(int i=0;i<N;i++){
while(cnt>1&&Cross(Ch[cnt-2]-Ch[cnt-1],Ch[cnt-2]-P[i])<=0)
cnt--;
Ch[cnt++]=P[i];
}
int vis=cnt;
for(int i=N-2;i>=0;i--){
while(cnt>vis&&Cross(Ch[cnt-2]-Ch[cnt-1],Ch[cnt-2]-P[i])<=0)
cnt--;
Ch[cnt++]=P[i];
}
int ans=0;
for(int i=0;i<cnt;i++)
for(int j=i+1;j<cnt;j++)
ans=max(ans,dis(Ch[i],Ch[j]));
printf("%d\n",ans);
return 0;
}
旋转卡壳代码
仔细观察Andrew求凸包的算法就会发现,初始点是会被重复计算两次的,所以凸包实际上点的个数应该是cnt-1(多算了一个)
给出一组数据
3
0 2
2 0
0 0
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cmath>
using namespace std;
const double eps=1e-8;
const double PI=acos(-1);
typedef struct{
int x,y;
}Point;
typedef Point Vector;
Point P[50005];
Point Ch[50005];
Vector operator - (Point a,Point b){
return Vector {a.x-b.x,a.y-b.y};
}
int Cross(Vector a,Vector b){
return a.x*b.y-a.y*b.x;
}
bool cmp(Point a,Point b){
if(a.x!=b.x) return a.x<b.x;
return a.y<b.y;
}
int dis(Point a,Point b){
return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y);
}
int main()
{
int N;
scanf("%d",&N);
for(int i=0;i<N;i++)
scanf("%d%d",&P[i].x,&P[i].y);
sort(P,P+N,cmp);
int cnt=0;
for(int i=0;i<N;i++){
while(cnt>1&&Cross(Ch[cnt-2]-Ch[cnt-1],Ch[cnt-2]-P[i])<=0)
cnt--;
Ch[cnt++]=P[i];
}
int vis=cnt;
for(int i=N-2;i>=0;i--){
while(cnt>vis&&Cross(Ch[cnt-2]-Ch[cnt-1],Ch[cnt-2]-P[i])<=0)
cnt--;
Ch[cnt++]=P[i];
}
cnt--;
//旋转卡壳:
int ans=0;
int j=2;
for(int i=0;i<cnt;i++){
while(abs(Cross(Ch[i]-Ch[i+1],Ch[i+1]-Ch[j]))<abs(Cross(Ch[i]-Ch[i+1],Ch[i+1]-Ch[j+1])))
j=(j+1)%cnt;
ans=max(ans,dis(Ch[i],Ch[j]));
}
printf("%d\n",ans);
return 0;
}
B - Wall
题目链接:POJ1113
题目大意:
给出一个n边形的n个顶点,现要修建一堵墙,将这个n边形围起来,要求墙上任意一点距离n边形至少距离为L,求墙的最小周长
分析
第一想法很简单,就是让这个n边形外扩L
①题目没有说这个n边形是凸多边形,画图即可发现,如果是凹多边形,转化为凸包可以满足条件,且墙周长更短
②在凸多边形的角的地方可以磨圆,即以凸多边形的顶点为圆心,L为半径画圆即可,所得圆与外扩L的n边形的边相切,得到一个扇形。不难证明扇形的圆心角的和必然为360°:
①扇形的圆心角和其所对角和为180°,其所对角为凸多边形的内角
②总共n个角,所以圆心角之和加凸多边形内角之和为180n
③n凸边形内角和180(n-2)
④相减得圆心角之和为360
因此答案为凸包的周长加上以L为半径的圆的周长
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cmath>
using namespace std;
const double eps=1e-8;
const double PI=acos(-1);
typedef struct{
int x,y;
}Point;
typedef Point Vector;
Point P[50005];
Point Ch[50005];
Vector operator - (Point a,Point b){
return Vector {a.x-b.x,a.y-b.y};
}
int Cross(Vector a,Vector b){
return a.x*b.y-a.y*b.x;
}
bool cmp(Point a,Point b){
if(a.x!=b.x) return a.x<b.x;
return a.y<b.y;
}
double dis(Point a,Point b){
return sqrt((double)(a.x-b.x)*(double)(a.x-b.x)+(double)(a.y-b.y)*(double)(a.y-b.y));
}
int main()
{
int N,L;
scanf("%d%d",&N,&L);
for(int i=0;i<N;i++)
scanf("%d%d",&P[i].x,&P[i].y);
sort(P,P+N,cmp);
int cnt=0;
for(int i=0;i<N;i++){
while(cnt>1&&Cross(Ch[cnt-2]-Ch[cnt-1],Ch[cnt-2]-P[i])<=0)
cnt--;
Ch[cnt++]=P[i];
}
int vis=cnt;
for(int i=N-2;i>=0;i--){
while(cnt>vis&&Cross(Ch[cnt-2]-Ch[cnt-1],Ch[cnt-2]-P[i])<=0)
cnt--;
Ch[cnt++]=P[i];
}
double ans=0;
for(int i=0;i<cnt;i++)
ans+=dis(Ch[i],Ch[(i+cnt-1)%cnt]);
ans+=2.0*PI*L;
printf("%.0f\n",ans);
return 0;
}
E - Triangle
题目大意:
给定平面坐标中N个点,从这N个点中任取三个,求组成的最大的三角形的面积是多少
分析
易得面积最大的三角形肯定由凸包上的三个点组成
朴素做法:
暴力枚举三个点
O
(
N
3
)
O(N^3)
O(N3)
优化:
枚举第一个顶点,旋转卡壳枚举第二个和第三个顶点,因为在凸包中两点固定时,第三个点按逆时针旋转时,所得三角形面积为单峰函数,所以复杂度大致降到了
O
(
N
2
)
O(N^2)
O(N2)
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cmath>
using namespace std;
const double eps=1e-8;
const double PI=acos(-1);
typedef struct{
int x,y;
}Point;
typedef Point Vector;
Point P[50005];
Point Ch[50005];
Vector operator - (Point a,Point b){
return Vector {a.x-b.x,a.y-b.y};
}
int Cross(Vector a,Vector b){
return a.x*b.y-a.y*b.x;
}
bool cmp(Point a,Point b){
if(a.x!=b.x) return a.x<b.x;
return a.y<b.y;
}
int main()
{
int N;
while(~scanf("%d",&N)&&N!=-1){
for(int i=0;i<N;i++)
scanf("%d%d",&P[i].x,&P[i].y);
sort(P,P+N,cmp);
int cnt=0;
for(int i=0;i<N;i++){
while(cnt>1&&Cross(Ch[cnt-2]-Ch[cnt-1],Ch[cnt-2]-P[i])<=0)
cnt--;
Ch[cnt++]=P[i];
}
int vis=cnt;
for(int i=N-2;i>=0;i--){
while(cnt>vis&&Cross(Ch[cnt-2]-Ch[cnt-1],Ch[cnt-2]-P[i])<=0)
cnt--;
Ch[cnt++]=P[i];
}
cnt--;
int ans=0;
int j=1;
int k=2;
for(int i=0;i<cnt;i++){
while(abs(Cross(Ch[i]-Ch[j],Ch[j]-Ch[k]))<abs(Cross(Ch[i]-Ch[j],Ch[j]-Ch[k+1])))
k=(k+1)%cnt;
while(abs(Cross(Ch[i]-Ch[j],Ch[j]-Ch[k]))<abs(Cross(Ch[i]-Ch[j+1],Ch[j+1]-Ch[k])))
j=(j+1)%cnt;
ans=max(ans,abs(Cross(Ch[i]-Ch[j],Ch[j]-Ch[k])));
}
printf("%.2f\n",(double)ans/2.0);
}
return 0;
}
K - Romeo Meets Juliet
题目链接:POJ-2189
这题暂时没想到和凸包有啥关系, 1 0 3 10^3 103的数据量也完全可以 N 2 N^2 N2暴力
代码
#include <cstdio>
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
int cow[1005];
int main()
{
int N,P,C;
scanf("%d%d%d",&N,&P,&C);
for(int i=1;i<=N;i++){
int temp;
scanf("%d",&temp);
cow[temp]++;
}
int ans=0;
for(int i=1;i<P;i++){
int cnt=0;
for(int j=i;j<P;j++){
cnt+=cow[j];
if(cnt>C) break;
ans=max(ans,j-i+1);
}
}
printf("%d\n",ans);
return 0;
}
L - Scrambled Polygon
题目链接:POJ-2007
题目大意:
给定一个n边形,n个顶点的坐标,保证其中一个顶点在原点,求这些顶点逆时针排序后的结果
分析
利用叉积判断顺逆时针关系
代码
#include<cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
typedef struct{
int x;
int y;
}Point;
typedef Point Vector;
int Cross(Vector a,Vector b){
return a.x*b.y-a.y*b.x;
}
bool cmp(Vector a,Vector b){
return a.x*b.y-a.y*b.x>0;
}
Vector V[1000];
int main() {
int x,y;
int cnt=0;
while(~scanf("%d%d",&x,&y))
V[cnt++]=Point{x-0,y-0};
sort(V+1,V+cnt,cmp);
for(int i=0;i<cnt;i++)
printf("(%d,%d)\n",V[i].x,V[i].y);
return 0;
}