题目概述:
有N个点,一个距离参数L
每个点坐标x,y
由这些点构造一个周长最短的图形使得所有点不在该图形外部,再构造另一个周长最短图形,使得旧图形在新图形内部且新图形上任何点到旧图形的距离等于L
输入:
第一行N,L,其后N行,每行x,y
输入只有一组
限制:
3<=N<=1000;1<=L<=1000;-10000<=x,y<=10000;
输出:
一个整数,新图形的周长,小数部分四舍五入到整数
输出只有一组
样例输入:
9 100
200 400
300 400
300 300
400 300
400 400
500 400
500 200
350 200
200 200
样例输出:
1628
讨论:
经典的求凸包老题,利用melkman算法,有些多余,但该算法是在线算法,任何时刻的deque数组的bot+1到top-1下标的点都构成凸包
题解状态:
164K,16MS,C++,1448B
#include<algorithm>
#include<string.h>
#include<stdio.h>
#include<math.h>
using namespace std;
#define INF 0x3f3f3f3f
#define maxx(a,b) ((a)>(b)?(a):(b))
#define minn(a,b) ((a)<(b)?(a):(b))
#define MAXN 1005
const double pi = atan(1.0) * 4;//比起任何常数,还是这个比较靠谱
struct point//点(或向量)的结构体,表示其横纵坐标
{
int x;
int y;
};
point points[MAXN];//原始数据存放于此
int deque[MAXN * 2];//处理算法的数组,类似于STL的双头队列(然而实质是循环队列)而借其名,然而STL的只能读取两头的元素,不能满足该题需要,*2则是扩张的需要,因为是从n处开始,因此两侧都可能用到,另外注意这里存的是对应points数组的下标,起到一个指针的作用
int top, bot;//top,bottom,代表队头和队尾,最后也作为算法结果的一部分
int N, L;//题目原生变量,N个点,距离L
inline double dis(point &a, point &b)//distance,两点间距离
{
double dx = a.x - b.x;
double dy = a.y - b.y;
return sqrt(dx*dx + dy*dy);
}
inline int xproduct(point &a, point &b)//cross product,向量积的z坐标,但由于只有平面坐标系,可仅考虑其正负的意义
{
return a.x*b.y - a.y*b.x;
}
inline int isleft(point &a, point &b, point &c)//判断点c是否在向量ab的逆时针方向,若在则返回一正数,若在顺时针方向则返回一负数,若c在直线ab上,返回0
{
point p, o;
p.x = b.x - a.x;
p.y = b.y - a.y;
o.x = c.x - b.x;
o.y = c.y - b.y;
return xproduct(p, o);//其实返回的是向量ab和向量bc的叉乘
}
inline void melkman(void)
{
bot = N - 1;
top = N;//首先定义队尾和队头
deque[top++] = 0;//假设数据合法,至少有3个点,先将前两个点入队
deque[top++] = 1;<span id="transmark"></span>
int p;//这个临时变量需要一直使用,因此没有在for内声明
for (p = 2; !isleft(points[deque[top - 2]], points[deque[top - 1]], points[p]); p++)//找一个点使得最初这三点不共线
deque[top - 1] = p;//注意,如果共线,会用找的点取代原来作为第二个参数的点,经过有序化的点进行这一操作是有意义的
deque[bot--] = p;
deque[top++] = p;//压入第三个节点,并作为起点/终点
if (isleft(points[deque[N]], points[deque[N + 1]], points[deque[N + 2]]) < 0) {//确保起始的三个点是逆时针顺序的,因为算法的结果就是逆时针顺序,而前三个点无法用算法主体判断
deque[N] ^= deque[N + 1];
deque[N + 1] ^= deque[N];
deque[N] ^= deque[N + 1];//这里用异或运算只是提醒一下存在这种方法交换数值变量
}
for (p++; p < N; p++) {//遍历剩余所有点,构造凸包
if (isleft(points[deque[top - 2]], points[deque[top - 1]], points[p]) <= 0 || isleft(points[deque[bot + 1]], points[deque[bot + 2]], points[p]) <= 0) {//如果新点在开头/末尾两个点非顺时针方向
while (isleft(points[deque[top - 2]], points[deque[top - 1]], points[p]) <= 0)
top--;//如果新点在旧凸包最后两个点所成向量的顺时针方向或共线,则原来最后一个点会在新凸包内部或边上,退掉这个点
deque[top++] = p;//新点在逆时针方向,确实可构成凸包
while (isleft(points[deque[bot + 1]], points[deque[bot + 2]], points[p]) <= 0)
bot++;//对于最初两个点同理
deque[bot--] = p;//同理
}
}
}
double fun()
{
for (int p = 0; p < N; p++) {
scanf("%d%d", &points[p].x, &points[p].y);//input
}//input ends here
melkman();
double sum = 0.0;
for (int p = bot + 1; p < top - 1; p++)
sum += dis(points[deque[p]], points[deque[p + 1]]);
sum += 2 * pi*L;
return sum;
}
int main(void)
{
//freopen("vs_cin.txt", "r", stdin);
//freopen("vs_cout.txt", "w", stdout);
while (~scanf("%d%d", &N, &L)) {//input
printf("%.0f\n", fun());//output
}
}
EOF