1. 程式人生 > >[Luogu 3958] NOIP2017 D2T1 奶酪

[Luogu 3958] NOIP2017 D2T1 奶酪

tdi const ini scan 搜索 using mem opera int

NOIP2017 D2T1 奶酪(Luogu 3958)


人生第一篇題解,多多關照吧。



一個比較容易想到的搜索。我用的BFS。
因為涉及到開根,所以記得開double。
首先將所有的球按z值從小到大排序,如果最下方的球與底面相離,或是最上方的球與頂面相離,直接Pass。
接下來預處理,記得先初始化,對於每一組球(i,j),計算兩球球心距離是否小於半徑×2,用一個bool數組e[i][j]記錄i能否到達j,避免BFS時重復計算。
我們會發現,可能不止一個球與底面相切或相交,也可能不止一個球與頂面相切或相交。
這就是說,BFS時起點和終點都可能不止一個,這給我們操作造成了一些麻煩(然而考場上我就這麽硬搜的居然AC了)。
其實,通過建立「超級起點」和「超級終點」,可以把這個BFS變得正常。


我們可以用結構體數組的第0個元素表示「超級起點」,第n+1個元素表示「超級終點」。
預處理時,走一遍所有的球,如果當前球可以做起點,就連上當前球和超級起點,e[0][i]=e[i][0]=1;終點亦然。
在跑BFS的時候,對於每一個球,依次判斷其能否到達0..n+1,當「超級終點」已被訪問或隊列已為空時結束搜索。
如果「超級終點」被訪問過說明搜到了,可以到達;否則無法到達。
/*想象一下我打完這篇文章後把文中所有的「點」一個個改成「球」*/

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <queue> using namespace std; const int MAXN=1010; bool vis[MAXN],e[MAXN][MAXN]; double h,r; int T,n; struct ball { double x,y,z; bool operator <(const ball &rhs) const { return z<rhs.z; } }s[MAXN]; double dis(ball a,ball b) { double t1=a.x-b.x,t2=a.y-b.y,t3=a.z-b.z; return
sqrt(t1*t1+t2*t2+t3*t3); } void Init() { memset(vis,0,sizeof vis);//初始化 memset(e,0,sizeof e);//初始化 for(int i=1;i<=n;++i) { if(s[i].z-r<=0)//超級起點 e[0][i]=e[i][0]=1; if(s[i].z+r>=h)//超級終點 e[n+1][i]=e[i][n+1]=1; } for(int i=1;i<n;++i) for(int j=i+1;j<=n;++j) e[i][j]=e[j][i]=dis(s[i],s[j])<=r*2;//i與j是否相連 } void BFS() { queue<int> q; q.push(0); vis[0]=1; while(!vis[n+1] && !q.empty()) { int x=q.front(); q.pop(); for(int i=0;i<=n+1;++i)//遍歷鄰接點 if(!vis[i] && e[x][i])//沒被訪問過且可到達 { q.push(i); vis[i]=1; } } } int main(int argc,char *argv[]) { scanf("%d",&T); while(T--) { scanf("%d %lf %lf",&n,&h,&r); for(int i=1;i<=n;++i) scanf("%lf %lf %lf",&s[i].x,&s[i].y,&s[i].z); sort(s+1,s+n+1);//排序 if(s[1].z-r>0 || s[n].z+r<h)//直接Pass的情況 { printf("No\n"); continue; } Init();//初始化+預處理 BFS(); printf(vis[n+1]?"Yes\n":"No\n");//判斷是否搜到 } return 0; }

NOIP2017唯一AC的一道題啊。

[Luogu 3958] NOIP2017 D2T1 奶酪