问题:Radar InstallationPOJ - 1328
假定海岸线是无限长的直线。陆地位于海岸线的一侧,海洋位于另一侧。每个小岛是位于海洋中的一个点。对于任何一个雷达的安装 (均位于海岸线上),只能覆盖 d 距离,因此海洋中的小岛被雷达安装所覆盖的条件是两者间的距离不超过 d 。
我们使用卡笛尔坐标系,将海岸线定义为 x 轴。海洋的一侧位于 x 轴上方,陆地的一侧位于下方。给定海洋中每个小岛的位置,并给定雷达安装的覆盖距离,您的任务是写一个程序,找出雷达安装的最少数量,使得所有的小岛都被覆盖。注意:小岛的位置以它的 x-y 坐标表示。
图 A: 雷达安装的示例输入
3 2 1 2 -3 1 2 1 1 2 0 2 0 0Sample Output:
Case 1: 2 Case 2: 1
分析:
问题可以抽象为:在给定的区间(ai,bi)里面,选取尽量少的点,使得每一个区间里面都有点被选到,策略有几种:
1.按照bi从小到大排序或者按照ai从小到大排序,之后排除掉所有大区间包含小区间(如果小区间有点被选中,则大区间肯定也同时有点被选中)的情况(由于有一边的大小关系已经通过排序确定了,所以只用挨个考察另一边的大小关系即可),这样就会得到一系列起点,终点都按照从小到大排序的区间。此时选点,就一直选某一个区间的终点,选起点也可以,但是这样就需要判断选的起点是不是最优的情况,而选终点的话,只用从头开始选,每一次选完就把已经有点被选的区间排除掉,从剩余的区间里面继续开始即可
2.按照起点排序,每一次选点都选某一个区间开头的那一个点,这个点存在于这个区间及之前所有区间中,但不存在于下一个区间中,这样做可以保证得到最优解(如果所谓的最优解中,对应的点在该法前面的话,一定不会包含该法中点所在的那个区间,不如该法好;如果所谓的最优解中,对应的点在该点的后面的话,假若不被包含于下一个区间,由于点存在的区间总数并没有增加,甚至可能减少,则完全可以被改点所替换;或者最优解中的点被下一个区间所包含,由于我们的策略中,之所以选取该区间的起点而不是下一个区间的起点,就是因为存在有下一个区间起点所不能包含于的前面的区间,所以最优解中的对应点是不可能包含于下一个区间的;前面这三句话分析了选区间起点确实可以保证找到最优解)。实际操作的时候不段尝试下一个区间的起点与已经尝试过的区间中终点最小的那个点的大小关系,这个最小的终点可以通过优先队列来得到。每一次发现可以选一个点之后,就清空队列,实际上也是把之前已经有点被选的区间给排除了,直到所有的区间被处理完
代码1(策略1):
#include<iostream> #include<queue> #include<cmath> #include<algorithm> using namespace std; int n,d,Count=0,INF=1<<30; priority_queue<double,vector<double>,greater<double> > que; struct interval{ double x,y; int mark; bool operator < (const interval& i){ return x-i.x<1e-6; } }ints[1010]; int main(){ double x,y;cin>>n>>d; while(n!=0||d!=0){ while(!que.empty())que.pop(); Count++; int ok=0; for(int i=1;i<=n;i++){ cin>>x>>y; if(y>d)ok=1; if(d!=0){ ints[i].x=x-sqrt(d*d-y*y); ints[i].y=x+sqrt(d*d-y*y); } ints[i].mark=1; //cout<<i<<":"<<ints[i].x<<" "<<ints[i].y<<endl; } if(d<=0){ cout<<"Case "<<Count<<": "<<-1<<endl; cin>>n>>d; continue; } if(ok){ cout<<"Case "<<Count<<": "<<-1<<endl; cin>>n>>d; continue; } sort(ints+1,ints+1+n); //cout<<"hi"<<endl; for(int i=n;i>1;i--){ for(int j=i-1;j>=1;j--) if(ints[i].y<=ints[j].y){ ints[j].mark=0; } } //cout<<"hihi"<<endl; //cout<<n<<":"<<ints[n].x<<' '<<ints[n].y<<endl; double last=-INF; int num=0; for(int i=1;i<=n;i++){ if(ints[i].mark==0)continue; //cout<<last<<' '<<i<<' '<<ints[i].x<<endl;//||last-ints[i].x<1e-6 if(last<ints[i].x){ num++; //cout<<i<<endl; last=ints[i].y; } } cout<<"Case "<<Count<<": "<<num<<endl; cin>>n>>d; } return 0; }
代码2(策略2):
#include<iostream> #include<queue> #include<cmath> #include<algorithm> using namespace std; int n,d,Count=0; priority_queue<double,vector<double>,greater<double> > que; struct interval{ double x,y; bool operator < (const interval& i){ return x-i.x<1e-6; } }ints[1010]; int main(){ double x,y;cin>>n>>d; while(n!=0||d!=0){ while(!que.empty())que.pop(); Count++; int ok=0; for(int i=1;i<=n;i++){ cin>>x>>y; if(y>d)ok=1; if(d!=0){ ints[i].x=x-sqrt(d*d-y*y); ints[i].y=x+sqrt(d*d-y*y); } } if(d<=0){ cout<<"Case "<<Count<<": "<<-1<<endl; cin>>n>>d; continue; } if(ok){ cout<<"Case "<<Count<<": "<<-1<<endl; cin>>n>>d; continue; } sort(ints+1,ints+1+n); int i=1,num=0; while(i<=n){ que.push(ints[i].y); while(i<n&&(ints[i+1].x<que.top()||abs(ints[i+1].x-que.top())<=1e-6)){ i++;que.push(ints[i].y); } num++;i++; while(!que.empty())que.pop(); } cout<<"Case "<<Count<<": "<<num<<endl; cin>>n>>d; } return 0; }
后记:
本来写的是第一种策略,结果一开是就WA了很久,之后尝试第二种策略,WA了更久,好伤心。。。幸亏POJ上有人提供了一些测试数据,debug之后总算AC了,总结起来有以下几点要注意:
1.新一轮循环开始的时候记得需要初始化的初始化
2.贪心法里面往往有一个循环,循环的开始条件,判断条件要思考准确,等号能不能加,初始值设为多少,如果没有考虑清楚就很容易在边界问题上WA
3.有一个个人的误区。。。如果浮点数比较大小,应该用<和>就足够了,判断等于的时候才可能会用abs()<1e-6这样的写法
总之这个题好伤我。。。继续努力。。。