做凸包题之前必须选一个正确的模板,自己写也行,但要保证正确,我就写错了,菜呀
凸包模板(Graham算法):
/*==================================================*\
| Graham求凸包 O(N * logN)
| CALL: nr = graham(pnt, int n, res); res[]为凸包点集;
\*==================================================*/
struct Point { double x, y; };
bool mult(Point sp, Point ep, Point op){
return (sp.x - op.x) * (ep.y - op.y)
>= (ep.x - op.x) * (sp.y - op.y);
}
bool operator < (const Point &l, const Point &r){
return l.y < r.y || (l.y == r.y && l.x < r.x);
}
int graham(Point pnt[], int n, Point res[]){
int i, len, k = 0, top = 1;
sort(pnt, pnt + n);
if (n == 0) return 0; res[0] = pnt[0];
if (n == 1) return 1; res[1] = pnt[1];
if (n == 2) return 2; res[2] = pnt[2];
for (i = 2; i < n; i++) {
while (top && mult(pnt[i], res[top], res[top-1]))
top--;
res[++top] = pnt[i];
}
len = top; res[++top] = pnt[n - 2];
for (i = n - 3; i >= 0; i--) {
while (top!=len && mult(pnt[i], res[top], res[top-1]))
top--;
res[++top] = pnt[i];
}
return top; // 返回凸包中点的个数
}
模板不一定总是可以用的,有时需要根据自己的需求做一点改动。
下面是我做的几道凸包题的解答。
题意:给出若干个点,让建一个围墙,包含所有的点,且距所有点的距离不小于L。
分析:该题实际就是求凸包边缘的长度,我运用了Graham算法。算自己独立写的第一道凸包吧。
代码:
#include<stdio.h>
#include <math.h>
#include<algorithm>
using namespace std;
#define PI acos(-1.0)
struct Point
{
int x,y;
}p[1005];
int stack[1005],tn;
bool Comp(Point a,Point b)
{
if(a.y==b.y)return a.x<b.x;
else return a.y<b.y;
}
int multi(Point p1, Point p2, Point p0)
{
return (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y);
}
double Distance(Point a,Point b){
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)*1.0);
}
bool Comp1(Point a,Point b)
{
int temp=multi(a,b,p[0]);
if(temp>0)return true;
else if(temp==0&&Distance(a,p[0])<Distance(b,p[0]))//!temp是错误的
return true;
return false;
}
void graham(int n)
{
int i;
for(i=0;i<3;i++)
stack[i]=i;
tn=2;
for(;i<n;i++)
{
while(multi(p[stack[tn]],p[i],p[stack[tn-1]])<=0)
{
tn--;
if(tn==0)break;
}
stack[++tn]=i;
}
}
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,Comp);//用for 直接线性时间,更快
sort(p+1,p+N,Comp1);
graham(N);
double total=0.0;
for(int i=0;i<tn;i++)
total+=Distance(p[stack[i]],p[stack[i+1]]);
total+=Distance(p[stack[tn]],p[0]);
total+=2*L*PI;
int sum=total;
if(total-sum>=0.5)sum++;
printf("%d\n",sum);
return 0;
}
题意:就是将所给点按凸包顺序输出,从原点开始。
分析:其实就是将除原点外的点进行极角排序。
代码:
#include <stdio.h>
#include <algorithm>
using namespace std;
struct Point
{
int x,y;
}p[55];
int multi(Point p1, Point p2, Point p0)
{
return (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y);
}
bool Comp(Point a,Point b)
{
int temp=multi(a,b,p[0]);
if(temp>0)return true;
else if(temp==0)//!temp是错误的//&&Distance(a,p[0])<Distance(b,p[0])
return true;
return false;
}
int main()
{
int n=0;
while(~scanf("%d%d",&p[n].x,&p[n].y))
n++;
sort(p+1,p+n,Comp);
for(int i=0;i<n;i++)
printf("(%d,%d)\n",p[i].x,p[i].y);
return 0;
}
题意:从所给的树中选取一定数量的树砍伐后作栅栏,能把剩下的树围住。每棵树都有相应的价值和长度,要求所砍的树的值最小,在值相同的情况下,选择数目少的。
分析:通过枚举,砍掉某些树,计算其价值总和,判断该价值是否比minv小(minv用于存放枚举时满足条件的被砍树总价值的最小值),若大于,则不再考虑该情况,这是很重要的一个剪枝。对剩下的树(点)求凸包周长,判断所砍树能否围住,能的话则保存该情况。
遇到的未解决的问题:graham(…)函数中的开始,个人觉得注释掉的for循环与sort(…)的所起的作用是一样的,但若用该for循环就是wrong answer。还请高手解释~~另外,觉得这题没考虑满足条件的被砍的树总价值相同时选择数目少的,也能AC,应该是测试数据不全面。
代码:
#include<stdio.h>
#include <math.h>
#include <algorithm>
using namespace std;
struct Point
{
int x,y,value;
double length;
}p[16],plist[16];
bool Comp(Point a,Point b)
{
if(a.y==b.y)return a.x<b.x;
else return a.y<b.y;
}
int multi(Point p1, Point p2, Point p0)
{
return (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y);
}
double Distance(Point a,Point b){
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)*1.0);
}
void swap(Point &a,Point &b)
{
Point temp;
temp=a;
a=b;
b=temp;
}
double graham(Point pnt[], int n, int res[]){
sort(pnt,pnt+n,Comp);
/*for(int i=1;i<n;i++)
if(pnt[i].y<pnt[0].y||(pnt[i].y==pnt[0].y&&pnt[i].x<pnt[0].x))
swap(pnt[i],pnt[0]);*/
int i, len, k = 0, top = 1;
if (n == 0) return 0.0; res[0] = 0;
if (n == 1) return 0.0; res[1] = 1;
if (n == 2) return 2.0*Distance(pnt[0],pnt[1]);
res[2] = 2;
for (i = 2; i < n; i++) {
while (top && multi(pnt[i], pnt[res[top]], pnt[res[top-1]])<=0)
top--;
res[++top] = i;
}
len = top; res[++top] =n-2;
for (i = n - 3; i >= 0; i--) {
while (top!=len && multi(pnt[i], pnt[res[top]], pnt[res[top-1]])<=0)
top--;
res[++top] = i;
}
double total=0.0;
for(int i=0;i<top-1;i++)
total+=Distance(pnt[res[i]],pnt[res[i+1]]);
total+=Distance(pnt[res[0]],pnt[res[top-1]]);
return total;
}
int main()
{
int count=0,n,minv,curv,pnum,hn,pfnum;
int store[16],stack[20];
double need_len,left,curl;
while(~scanf("%d",&n)&&n)
{
count++;
minv=999999;
for(int i=0;i<n;i++)
scanf("%d%d%d%lf",&p[i].x,&p[i].y,&p[i].value,&p[i].length);
for(int i=1;i<(2<<n);i++)
{
curv=pnum=curl=0;
for(int j=0;j<n;j++)
{
int s=1<<j;
if(s&i)//被砍
{
curv+=p[j].value;
curl+=p[j].length;
}
else plist[pnum++]=p[j];
}
//if(curv>minv)continue;
need_len=graham(plist,pnum,stack);
if(need_len<=curl&&curv<minv)
{
minv=curv;
hn=0;
for(int k=0;k<n;k++)
if((1<<k)&i)store[hn++]=k+1;
left=curl-need_len;
//pfnum=pnum;
}/*
else if(need_len<=curl&&curv==minv&&pnum>pfnum)//有相同最小量时选取被砍树数目最少的,但不考虑也能AC
{
minv=curv;
hn=0;
for(int k=0;k<n;k++)
if((1<<k)&i)store[hn++]=k+1;
left=curl-need_len;
pfnum=pnum;
}*/
}
printf("Forest %d\n",count);
printf("Cut these trees:");
for(int i=0;i<hn;i++)
printf(" %d",store[i]);
printf("\nExtra wood: %.2lf\n\n",left);
}
return 0;
}
题意:给一些农场边界上的点,问是否可以由这些点唯一确定农场的形状(一个凸多边形)。
分析:这题我也看了discuss才明白,自己英语很菜,没读懂题目。可以这样理解:如果凸多边形某边上没有3个点,那就可以再添加一个点形成新的凸包。如果都存在3个点,那么再加一点形成的多边形就是凹的了。
我是这样做的,由于所给点都是凸包上的或凸包的边上的,那么找到最低点后再按极角排序后,点的顺序就是绕多边形一周的顺序(最后一条边除外,因为Distance 是近的优先级高)。然后套凸包模板,找到凸包的点(我这调试发现点的顺序是顺时针的,就排了序)。因为要保证边上都至少有三个点,所以凸包每两点之间必然还有点,遍历判断一下就好了。
代码:
#include <stdio.h> #include <math.h> #include<algorithm> using namespace std; struct Point { int x,y; }p[1005]; int multi(Point p1, Point p2, Point p0) { return (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y); } double Distance(Point a,Point b){ return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)*1.0); } bool Comp(Point a,Point b) { int temp=multi(a,b,p[0]); if(temp>0)return true; else if(temp==0&&Distance(a,p[0])<Distance(b,p[0])) return true; return false; } int graham(Point pnt[], int n, int res[]){ int i, len, k = 0, top = 1; if (n == 0) return 0; res[0] = 0; if (n == 1) return 1; res[1] = 1; if (n == 2) return 2; res[2] = 2; for (i = 2; i < n; i++) { while (top && multi(pnt[i], pnt[res[top]], pnt[res[top-1]])<=0) top--; res[++top] = i; } len = top; res[++top] =n-2; for (i = n - 3; i >= 0; i--) { while (top!=len && multi(pnt[i], pnt[res[top]], pnt[res[top-1]])<=0) top--; res[++top] = i; } return top; // 返回凸包中点的个数 } void swap(Point &a,Point &b) { Point temp; temp=a; a=b; b=temp; } int main() { int T,flag; int n; int stack[1005],tn; scanf("%d",&T); while(T--) { scanf("%d",&n); for(int i=0;i<n;i++) scanf("%d%d",&p[i].x,&p[i].y); if(n<6) { printf("NO\n"); continue; } for(int i=1;i<n;i++)//选取最低点 if(p[i].y<p[0].y||p[i].y==p[0].y&&p[i].x<p[0].x) swap(p[0],p[i]); sort(p+1,p+n,Comp);//按极角排序(相对于p[0]) int num=graham(p,n,stack); sort(stack,stack+num); flag=1; for(int i=1;i<num;i++) if(stack[i]-stack[i-1]==1) {flag=0;break;} if(num==n)flag=0; if(flag)printf("YES\n"); else printf("NO\n"); } return 0; }
题意:给出一些树的坐标,围着树做篱笆,以使牧场面积最大,每头牛要50平方米才能养活,问最多能养多少只羊。
分析:先求凸包,后求面积。
代码:
#include<stdio.h>
#include <math.h>
#include<algorithm>
using namespace std;
struct Point
{
int x,y;
}p[10002];
int multi(Point p1, Point p2, Point p0)
{
return (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y);
}
double Distance(Point a,Point b){
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)*1.0);
}
bool Comp(Point a,Point b)
{
int temp=multi(a,b,p[0]);
if(temp>0)return true;
else if(temp==0&&Distance(a,p[0])<Distance(b,p[0]))
return true;
return false;
}
void swap(Point &a,Point &b)
{
Point tmp;
tmp=a;
a=b;
b=tmp;
}
int graham(Point pnt[], int n, int res[]){
int i, len, k = 0, top = 1;
if (n == 0) return 0; res[0] = 0;
if (n == 1) return 1; res[1] = 1;
if (n == 2) return 2; res[2] = 2;
for (i = 2; i < n; i++) {
while (top && multi(pnt[i], pnt[res[top]], pnt[res[top-1]])<=0)
top--;
res[++top] = i;
}
len = top; res[++top] =n-2;
for (i = n - 3; i >= 0; i--) {
while (top!=len && multi(pnt[i], pnt[res[top]], pnt[res[top-1]])<=0)
top--;
res[++top] = i;
}
return top; // 返回凸包中点的个数
}
int det(int sx,int sy,int ex,int ey)
{
return sx*ey-sy*ex;
}
int main()
{
int n;
int stack[10002],tn;
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d%d",&p[i].x,&p[i].y);
for(int i=1;i<n;i++)
if(p[i].y<p[0].y||p[i].y==p[0].y&&p[i].x<p[0].x)
swap(p[i],p[0]);
sort(p,p+n,Comp);
tn=graham(p,n,stack);
int total=0;
for(int i=1;i<=tn;i++)
total+=det(p[stack[i-1]].x,p[stack[i-1]].y,p[stack[i%tn]].x,p[stack[i%tn]].y);
//total+=det(p[stack[tn-1]].x,p[stack[tn-1]].y,p[0].x,p[0].y);
if(total<0)total*=-1;
total=total/100;
printf("%d\n",total);
return 0;
}