凸包求法 POJ 1113 Wall 凸包
題目大意:
有個愚蠢的皇帝要你造牆將城堡圍起來,城堡的頂點有N個,牆必須離城堡的邊至少L單位遠,並且牆的總長度儘量小。求此長度?
因為牆的長度要儘量小,所以牆不能凹進去,一定是一個凸多邊形。如圖,最終的牆類似虛線部分,由凸包的周長和一個半徑L的圓構成,於是求出凸包就搞定了。因為走一圈,經過拐點時,所形成的扇形的內角和是360度,總會形成一個完整的圓。故結果等於,凸包周長+一個完整的圓周長。
值得注意的是,這種題目精度往往比較坑,四捨五入!=(int)型別強制轉換。
下面的程式套用的模板,比較全面,所以保留了lf(本題中int就行)。
求凸包主要思路就是先排序(y為第一關鍵字,x為第二關鍵字,不排序的話有可能會從最低點出發沿著左半凸走,每一次都是非法路徑,就把點刪完了,找不到凸包),找到左下角與右上角的兩個點將整個凸包分為兩部分(因為找右半凸的時候有可能找到左半凸上邊的點,因為不合法就會刪掉這個點,那麼之後我們找左半凸的時候就會受影響)。用叉乘判斷是否合法,叉乘結果為負時說明新的邊在舊的邊右側,而因為我們是逆時針找凸包的(如果要順時針找,那麼叉乘為正不合法),凸包又是一個凸多邊形,所以整個圖形應當在任一凸包上邊的左側,也就是說,沿著凸包走只能左轉。
通過這種方法就求得了凸包。
# include <cstdio>
# include <cstring>
# include <cstdlib>
# include <iostream>
# include <cmath>
# include <algorithm>
using namespace std;
#define rep(i,l,r) for(int i=l;i<=r;i++)
#define drep(i,r,l) for(int i=r;i>=l;i--)
#define min(a,b) (a<b?a:b)
#define max(a,b) (a>b?a:b)
#define LL long long
#define sqr(x) ((x)*(x))
#define suf(x) (x==n?1:x+1)
#define pi 3.1415926535897932384626433832795
const int N = 1010;
int n, l;
struct point{
double x , y;
point(){}
point(double x1 , double y1) {x = x1; y = y1;}
inline double operator%(const point &a)const{//叉乘
return x*a.y - y*a.x;
}
inline double operator*(const point &a)const{//點積
return x*a.x + y*a.y;
}
inline point operator+(const point &a)const{
return point(x + a.x , y + a.y);
}
inline point operator-(const point &a)const{
return point(x - a.x , y - a.y);
}
inline point operator*(double k)const{
return point(x*k , y*k);
}
inline point operator/(double k)const{
return point(x/k , y/k);
}
}a[N], b[N];
inline bool cmp(const point &a , const point &b){
//以y為第一關鍵字,x為第二關鍵字排序,保證從下到上
return a.y < b.y || a.y == b.y && a.x < b.x;
}
inline double Cross(point &a , point &b , point &c){
return (b-a) % (c-a);//向量ab叉乘向量ac
}
inline double dis(point &a , point &b){//兩點之間距離
return sqrt(sqr(a.x-b.x) + sqr(a.y-b.y));
}
inline void Tubao(){
sort(a+1, a+n+1, cmp);
int m = 0;
rep(i,1,n){
while (m > 1 && Cross(b[m-1] , b[m] , a[i]) <= 0) m--;
//只有一條邊時一定合法,叉乘<0時不合法,把中間所有不合法的點都去掉,加入這個新的點
b[++m] = a[i];//合法就儲存
}
int j = m;
drep(i,n,1){
while (m > j && Cross(b[m-1] , b[m] , a[i]) <= 0) m--;
b[++m] = a[i];
}
n = m;
rep(i,1,n) a[i] = b[i];//返回到a(可無)
}
inline void Init(){
scanf("%d%d", &n, &l);
rep(i,1,n)
scanf("%lf%lf" , &a[i].x , &a[i].y);
Tubao();
}
inline void work(){
double ans = 0;
for(int i=1; i<n; i++){
ans += dis(a[i], a[i+1]);//凸包邊長
}
ans += (2 * l) * pi;//圓周長(四捨五入->強轉int)
printf("%d", (int)(ans + 0.5));
/*for(int i=1; i<=n; i++){
printf("\n%lf %lf", a[i].x, a[i].y);
}*/
}
int main(){
//while(scanf("%d%d", &n, &l) != EOF){
Init();
work();
//}
return 0;
}
這裡給大家提供另一個方法,就是排序的時候按照極角排序,其實就是按照每個點與最低點連線的斜率從小到大排序(為什麼用最低點呢?其實按理說找任意的點都是可以的,只不過我們為了不處理負斜率就選取最低點),這樣的話就不用找最高點了,從最低點出發繞一圈即可。可以用反證法證明在尋找右半凸時不會找到左半凸,假設先找到了左半凸,那麼就說明有至少一個右半凸上的點(連線)的斜率大於這個左半凸上的點(連線)的斜率,那麼就是說從這個右半凸上的點走到這個左半凸上的點會向右轉,不滿足在凸包上的點的性質——只向右轉。所以說得證。
下面附上程式碼:
#include<stdio.h>
#include<math.h>
#include<algorithm>
using namespace std;
const double pi = acos(-1.0);
struct point{
int x,y;
}a[100010],stack[100010];
double sum=0;
int n,k;
double dis(point a,point b){
return sqrt((double)(a.x-b.x) * (a.x-b.x) + (a.y-b.y) * (a.y-b.y));
}
int cj(point p0, point p1, point p2) {
return (p1.x-p0.x) * (p2.y-p0.y) - (p1.y-p0.y) * (p2.x-p0.x);
}
bool cmp(const point &aa, const point &b)
{
int ans = cj(a[0], aa, b);
if(ans > 0) return true;
else if(ans == 0 && dis(a[0], aa) < dis(a[0], b)) return true;
else return false;
}//極角排序
int main(){
int pos = 0;
scanf("%d%d", &n, &k);
point p0;
scanf("%d%d", &a[0].x, &a[0].y);
p0.x = a[0].x, p0.y = a[0].y;
for(int i=1; i<n; i++){
scanf("%d%d", &a[i].x, &a[i].y);
if((p0.y > a[i].y) || (p0.y == a[i].y) && (p0.x > a[i].x)){
p0.x = a[i].x, p0.y = a[i].y;
pos = i;
}
}
a[pos] = a[0];
a[0] = p0;
int top = 1;
sort(a+1, a+n, cmp);
if(n == 1){
top=0;stack[0]=a[0];
}
if(n == 2){
top = 1;
stack[0] = a[0];
stack[1] = a[1];
}
if(n > 2){
stack[0] = a[0], stack[1] = a[1];
for(int i=2; i<n; i++){
while(top>0 && cj(stack[top-1], stack[top], a[i]) <= 0) top--;
stack[++top] = a[i];
}
}
for(int i=0; i<=top; i++)
sum += dis(stack[i], stack[(i+1) % (top+1)]);
printf("%d", (int)(sum + 2 * pi * k + 0.5));
}
並不是筆者的程式碼,懶得寫了,所以說湊合著看看吧,主要是排序那一塊。