1. 程式人生 > >【 貪心 進階總結 】【 來自一本通提高篇 】

【 貪心 進階總結 】【 來自一本通提高篇 】

區間與區間的最大不覆蓋數—套板子(區間右端點從小到大排序,選擇不衝突的,儘可能的選)簡記為線段覆蓋問題

區間內最少單點滿足覆蓋要求,不管是各個區間需要一個點還是一個子區間(這個子區間是可連續、可不連續的)

按照區間右端點從小到大排序,依次滿足各個區間的要求,如此下來種的樹(需要的點)最少。

如果想不出貪心,線段樹+優化亦可作。

選擇儘量少的區間覆蓋指定線段區間。

【 模板 】將所有的區間按照左端點從小到大排序,對於當前查詢的起點s ,選擇覆蓋點 s 的區間中右端點中最大的一個,並將 s 更新為此區間的右端點,(這樣區間數目+1)直到s>=整個線段長度。(在s時刻,找一個左端點小於等於s的最大右端點)

對於這道題,需要進行一些轉化——每個澆灌噴頭受限於所能噴到的最短距離,它的最短距離是半徑到達草坪上端。此時,如果將整個草坪長度想象為 x軸的正半軸,那麼,我們應該根據勾股定理找到水平距離,即(\sqrt{r^{2}-(\frac{W}{2})^{2}})。把當前點的pos減去水平距離作為左端點,pos+水平距離作為右端點。然後就是套上面的板子↑。

PS:一定要理解了過程再寫,一定要理解了過程再寫,一定要理解了過程再寫!!!

附上AC程式碼:

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define N 300005

using namespace std;

const double Eps = 1e-5 ;

inline int wread(){
    char c=getchar ();int flag=1,wans=0;
    while (c<'0'||c>'9'){if (c=='-') flag=-1;c=getchar ();}
    while (c>='0'&&c<='9'){wans=wans*10+c-'0';c=getchar ();}
    return wans*=flag;
}

int T;
int n,L,W;
struct node {double lx,rx;int pos,r;}a[N];
bool e666 (node x,node y){return x.lx<y.lx;}

int main (){
	T=wread();
	while (T--){
		memset (a,0,sizeof a);
		n=wread();L=wread();W=wread();
		for (int i=1;i<=n;++i){
			a[i].pos=wread(),a[i].r=wread();
			if (a[i].r*a[i].r-(W*1.0/2.0)*(W*1.0/2.0)<0)	a[i].pos=-1,a[i].lx=-1,a[i].rx=-1;
			//對於不能覆蓋完草坪上端的一個圓(例如樣例1中的那個夾在草坪中間的圓) 肯定是不會選它的 
			else {
				a[i].lx=max (0.000000,(double)a[i].pos-sqrt((double)a[i].r*(double)a[i].r-(W*1.0/2.0)*(W*1.0/2.0)));
				a[i].rx=a[i].pos+sqrt((double)a[i].r*(double)a[i].r-(W*1.0/2.0)*(W*1.0/2.0));					
			}
		}

		sort (a+1,a+n+1,e666);	
		
		int nx=1; 
		double s=0;//起點 
		bool F=true;//判斷是否能覆蓋完整個區間 
		int ans=0;//記錄 
		
		while (nx<=n){
			ans++;
			double re=s;
			while (a[nx].lx<=re && nx<=n){//模板 
				if (a[nx].rx>=re)	s=max(s,a[nx].rx);
				nx++;
			}
			if (s>=L)	{break;}
			if (re==s)	{F=false;break;}//未更新,請畫圖____即當前區間與下個區間之間 一定有一段距離,不符合全部覆蓋的要求,退出 
		}
		if (!F)	puts("-1");
		else	printf("%d\n",ans);
	}
	return 0;
}

未完待續...