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),任何大於此值的參數都不能進行acosconst 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;}