Time limit: 3.000 seconds
限时:3.000秒
Background
背景
The saying "You can't see the wood for the trees" is not only a cliche, but is also incorrect. The real problem is that you can't see the trees for the wood. If you stand in the middle of a "wood" (in NZ terms, a patch of bush), the trees tend to obscure each other and the number of distinct trees you can actually see is quite small. This is especially true if the trees are planted in rows and columns (as in a pine plantation), because they tend to line up. The purpose of this problem is to find how many distinct trees you can see from an arbitrary point in a pine plantation (assumed to stretch "for ever").
有句俗话“不能见树不见林”,这不仅仅是一句陈词滥调,而且是错误的。真正的问题是你不能见林不见树。如果你站在树林(在新西兰词汇中是一片灌木的意思)的中间,树木就会相互遮挡,你能分辨清楚的树木很少。这种情况在树木整齐的按行列排布(比如人工松林)时最为突出。本问题是要你来计算当站在人工松林中任意一个点时,你能看清的树有多少。
Problem
问题
You can only see a distinct tree if no part of its trunk is obscured by a nearer tree--that is if both sides of the trunk can be seen, with a discernible gap between them and the trunks of all trees closer to you. Also, you can't see a tree if it is apparently "too small". For definiteness, "not too small" and "discernible gap" will mean that the angle subtended at your eye is greater than 0.01 degrees (you are assumed to use one eye for observing). Thus the two trees marked ○ obscure at least the trees marked ● from the given view point.
只要当一棵树的树干完全没有被附近的树木遮挡时你才能看得清楚——也就是说树干的两侧都可以看到,而且两侧离那些更靠近你的树木之间还有一个可以分辨的间隙。同样,你不可能看到一棵“太细”的树。“不太细”和“可分辨间隙”的严格定义是相对你眼睛的视角要大于0.01度(你可以认为是用一只眼睛来观查的)。上图中,按照视角的位置,那两棵白色的树至少将所有灰色的树都挡住了。
Write a program that will determine the number of trees visible under these assumptions, given the diameter of the trees, and the coordinates of a viewing position. Because the grid is infinite, the origin is unimportant, and the coordinates will be numbers between 0 and 1.
给定树木的直径,以及眼睛的坐标位置,你写一个程序计算出在这些条件下能看清多少树。由于网格无限大,坐标原点就无所谓了,所给的坐标值都介于0到1之间。
Input
输入
Input will consist of a series of lines, each line containing three real numbers of the form 0.nn. The first number will be the trunk diameter--all trees will be assumed to be cylinders of exactly this diameter, with their centres placed exactly on the points of a rectangular grid with a spacing of one unit. The next two numbers will be the x and y coordinates of the observer. To avoid potential problems, say by being too close to a tree, we will guarantee that diameter≤x,y≤(1-diameter). To avoid problems with trees being too small you may assume that diameter≥0.1. The file will be terminated by a line consisting of three zeroes.
输入由多行组成,每行都包含三个形如0.nn的实数。第一行第一个数为树干的直径——所有树都认为是一个圆柱体,该值为其直径,圆柱体的中心都位于坐标网格的交点上,各相距一个单位。后两个数为观查者的x和y坐标。为了避免出现潜在的问题,我们保证直径≤x,y≤(1-直径)。为避免树木过小,你可以假定树木直径≥0.1。输入的数据由一行3个0表示结束。
Output
输出
Output will consist of a series of lines, one for each line of the input. Each line will consist of the number of trees of the given size, visible from the given position.
输出由多行组成,每行输入对应一行输出。每行都按给定的树木尺寸和眼睛位置打印出可见的树木数量。
Sample input
示例输入
0.10 0.46 0.38
0 0 0
Sample output
示例输出
128
Analysis
分析
截止这篇文章发出,UVa OJ上AC这道题的人刚刚超过300(包括不才)。事实上这道题并不难,学过高中几何的同学都完全可以做的出来,可能是大家都没看太懂题目吧?
算法的关键就在于如何判断两棵树是否相互遮挡。每棵树的坐标都为整数,树的宽度和眼睛坐标是给定的,通过这些数据就可以来判断遮挡了。请看下图(为方便描述,树的直径和眼睛点坐标可能与题目要求不符):
圆圈代表树,所有直线的交点为眼睛坐标,显然两棵白色的树对于当前的眼睛坐标来说是没有相互遮挡的。题目要求树干与眼睛点形成的夹角不能小于0.01度。按树干为最细的0.1计算,(0.1/2)/sin(0.01)≈286.48。也就是说当树干直径为0.1时,树干的中心必须与眼睛相距286以上才会使得其夹角小于0.01度,但中间隔了286棵树还没有被挡住是不可能出现的事,因此这种情况可以忽略。那么就只需要判断两棵树干之间是否满足最小的角度了。在上图中,蓝色部分的角度就是两棵树干之间的夹角,设为a;黄色部分的角度分别为两棵树干与眼睛形成的夹角的一半,设为b1和b2;由蓝色线条围成的角度(两棵树干的中心到眼睛)设为c,很显然有a=c-b1-b2。如果两棵树相互遮挡,必有c-b1-b2<0。利用这一点就可以方便的做出判断了。c的角度可由两个边向量做内积来计算,b1和b2可由反三角函数来计算,都是非常简单的。
接下来的问题就是如何遍例了。跟据测算,离眼睛最远且能看到树木不会超过10,因此遍例超过20×20=400棵树是没有必要的。根据这一点可以写一个四重循环,对于每一棵树都把其它所有的树遍例一次,判断它是否被遮挡。然而离眼睛点近的树不可能被更远的树挡住,因此可以优化这个遍例的过程。如下图所示:
按照颜色由浅到深的顺序遍例,就可以减少大量多余的判断。显然浅灰色的一圈只需判断是否被白色的挡住,而深灰色的只需判断是否被白色和浅灰色的挡住。当然,还可以更加的优化,我们可以比较精确的计算出一棵树可能被里面的哪几棵树挡住,但这样做的代码会很长,对于解这道题来说是没有太大意义的(想冲击0秒的同学可以尝试)。
Solution
解答
#include <iostream> #include <cmath> using namespace std; //用于存储坐标的结构体 struct POINT{int x; int y;}; //主函数 int main(void) { //dRad为180/pi,用于弧度到角度的转换, const float fRad = 57.295779513082320876798154814105f; //dMax为cos(0.01),任何大于此值的参数都不能进行acos const float fMax = 0.99999998476912904932780850903444f; //循环处理每一组输入的数据,d为直径,x和y为观查者坐标 for (float d, x, y; cin >> d >> x >> y && d != 0; ) { //将所有值放大100倍并取整,一可加快运算,二可保证精度 POINT Eye = {int(x * 100 + 0.5), int(y * 100 + 0.5)}; int nDiam = (int)(d * 100 + 0.5), nCnt = 0; //依次由里圈向外层层遍例每棵树,检查是否被里面的树遮挡 for (int iBeg = 0, iEnd = 100; iEnd <= 1000; iBeg -= 100, iEnd += 100) { //遍例外面这一圈的树 for (int i = 0; i < iEnd - iBeg; i += 100) { POINT Out[4] = {//一圈分别为左边、上边 {iBeg, iBeg + i}, {iBeg + i, iEnd}, //右边和下边 {iEnd, iEnd - i}, {iEnd - i, iBeg}}; //遍例四条边上的树 for (int j = 0; j < 4; j++) { //遍例圈里面所有的树,In为树的坐标 POINT In = {iBeg + 100, iBeg + 100}; for (; In.y <= iEnd - 100; In.y += 100) { for (In.x = iBeg + 100; In.x <= iEnd - 100; In.x += 100) {//以下算法判定两棵树是否遮挡 //分别建立里面树和处面树与眼睛坐标的向量 POINT NVec = {In.x - Eye.x, In.y - Eye.y}; POINT FVec = {Out[j].x - Eye.x, Out[j].y - Eye.y}; //两个向量求内积 int nProd = NVec.x * FVec.x + NVec.y * FVec.y; //求两个向量的模 float fNMod = sqrt((float)(NVec.x * NVec.x + NVec.y * NVec.y)); float fFMod = sqrt((float)(FVec.x * FVec.x + FVec.y * FVec.y)); //内积公式求夹角 float fACOS = nProd / (fNMod * fFMod); if (fACOS >= fMax) { //夹角不能大于cos(0.01) break; } //求出夹角角度 float fAng = acos(fACOS) * fRad; //分别计算两棵树干自身与眼睛形成的的夹角 fNMod = asin((float)nDiam / 2.0f / fNMod) * fRad; fFMod = asin((float)nDiam / 2.0f / fFMod) * fRad; //判断是否遮挡,如果有则跳出循环 if (fAng - fNMod - fFMod <= 0.01f) { break; } } //未完成内循环,说明存在遮挡,继续向外跳出 if (In.x <= iEnd - 100) { break; } } //累计可见树的个数 nCnt += (In.y > iEnd - 100); } } } //输出结果 cout << nCnt << endl; } return 0; }
作者:王雨濛;新浪微博:@吉祥村码农;来源:《程序控》博客 -- http://www.cnblogs.com/devymex/ 此文章版权归作者所有(有特别声明的除外),转载必须注明作者及来源。您不能用于商业目的也不能修改原文内容。 |