導彈攔截

归游發表於2024-10-15

題目

\(\\\)
某國為了防禦敵國的導彈襲擊,發展出一種導彈攔截系統。但是這種導彈攔截系統有一個缺陷:雖然它的第一發炮彈能夠到達任意的高度,但是以後每一發炮彈都不能高於前一發的高度。某天,雷達捕捉到敵國的導彈來襲。由於該系統還在試用階段,所以只有一套系統,因此有可能不能攔截所有的導彈。

輸入導彈依次飛來的高度,計算這套系統最多能攔截多少導彈,如果要攔截所有導彈最少要配備多少套這種導彈攔截系統
\(\\\)
經典的DP,適合打好基礎
\(\\\)

第一問

求最長不下降子序列
定義\(dp_i\) 為以第\(i\)個導彈高度為結尾,攔截的最多的導彈
\(h[i]\)為第\(i\)個導彈高度
\(\\\)
所以根據dp的通常思考方式,尋找\(dp_i\)之間的關係
\(\\\)
如果\(\exist j<i 且 h[j]>h[i]\) 那麼這兩個導彈就能被同一個導彈攔截系統
\(\\\)
同理\(\exist j<i 且 h[j]>h[i]\) 那麼\(dp_i=dp_j+1\)
\(\\\)
此時以第\(j\)個導彈高度為結尾的序列長度為\(dp_j\),而\(h[j]>h[i]\)
也可以攔截第\(i\)個導彈
\(\\\)
\(dp_i可以由多個dp_j得到,所以\displaystyle dp_i=max_{j<i, h[j]>h[i]} (dp_i,dp_j+1)\)

第二問

求最少的不下降子序列個數
貪心,當前已有的系統攔截不到,就增加系統
定義\(dp_i\)為第i個導彈高度為結尾的第\(dp_i\)個系統
\(\\\)
如果\(\exist j<i 且 h[j]<h[i]\) 那麼這兩個導彈就不能被同一個導彈攔截系統,需要新開一個系統,且新系統的攔截最後一個導彈為h[i]
如果\(\exist j<i 且 h[j]>h[i]\) 那麼這個導彈就可以被之前攔截h[j]的導彈攔截,就不需要開新系統
\(\\\)
同理\(\exist j<i 且 h[j]<h[i]\) 那麼\(dp_i=dp_j+1\)
\(\\\)
\(dp_i可以由多個dp_j得到,所以\displaystyle dp_i=max_{j<i, h[j]<h[i]} (dp_i,dp_j+1)\)
\(\\\)
--小疑問:可能會有人問,為什麼求最少的不下降序列個數反而還要取max
答:這樣取max才能使每一個導彈被攔截

\(\\\)

CODE

#include<cstring>
#include<iostream>

using namespace std;
int n;
int m[100000+10];
int f[100000+10];
int ans[100000+10];
int main(){
	ios::sync_with_stdio(0);
	int a;
	int len=0;
	memset(ans,0,sizeof(ans));
	while(cin>>a){
		m[++len]=a;
		f[len]=1;
	} 
	int maxx=0; 
	for(int i=2;i<=len;++i){
		for(int j=1;j<i;++j)
			if(m[i]<=m[j]) 
				f[i]=max(f[i],f[j]+1);
		maxx=max(f[i],maxx);
	}
	cout<<maxx<<endl;
	maxx=0;
	memset(f,1,sizeof(f));
	
	for(int i=1;i<=len;++i){
		f[i]=1;
		for(int j=1;j<i;++j)
			if(m[j]<m[i])
				f[i]=max(f[i],f[j]+1);
		maxx=max(maxx,f[i]);//有可能後面的導彈已經被前面已有的系統攔截了,不需要開新系統,而此時找到的最大值即是剛好攔截所有導彈的系統數量
	}
	cout<<maxx<<endl;
	return 0;
}

以上的code可以過1e5級別的資料

而此題可以考慮最佳化

最佳化

1.第一問

根據\(\displaystyle dp_i=max_{j<i, h[j]>h[i]} (dp_i,dp_j+1)\)

\(\\\)
我們有一定的時間在尋找\(max(dp_j+1)\),這導致了我們每次尋找時的複雜度為\(O(N)\)
根據題目的複雜度要求,我們應該考慮\(log_{2}N\)複雜度的尋找方法
對於\(\forall dp_j\in[0,n] \ and \ n<1e5\)
而且我們只需要最大值
\(\\\)
這時如果我們利用桶排序的思想,就能快速查詢,並且存最大值

\(f[x]\)長度為x的最長不上升子序列的末端高度
\(\\\)
對於\(\forall x>1 , \exist y<x,f[x]<f[y]\)(單調遞減)
\(\\\)
所以當我們考慮\(h[i]\)時,我們只需要在已知長度中找\(f[j]<=h[i](j為符合該條件的最大值),f[j+1]=h[i]\)
\(\\\)
而由於這種序列是單調遞減,所以二分查詢就可以,複雜度就降到\(O(log_2N)\)

第二問

\(\displaystyle dp_i=max_{j<i, h[j]<h[i]} (dp_i,dp_j+1)\)
\(f[x]\)第x個系統的攔截的末端高度
\(\\\)
對於\(\forall x>0, \exist y>x,f[x]<f[y]\)(單調遞增)

所以對於\(h[i]\),開一個新系統,我們只需要考慮,不\(\exists x,f[x]>h[i]\),此時沒有系統攔截的高度超過當前導彈,所以需要一個新系統

#include<bits/stdc++.h>

using namespace std;
int n=0;
const int maxn=1e5+10;
int t;
int h[maxn],f[maxn];

int main(){
    while(~scanf("%d",&h[++n])); --n;
	t=1;memset(f,0,sizeof(f));
	//f[i]表示長度i的序列結尾的高度 
	for(int i=1;i<=n;++i){
		int l=1,r=t;//當前長度的範圍
		
		while(l<=r){//查詢該高度可以拼接的最長長度 
			int mid=(l+r)>>1;
			if(f[mid]>=h[i]) l=mid+1;
			else r=mid-1;
		}
		//最後二分找到的 f[l]<h[i] && f[r]>=h[i] && r+1=l 
		//所以 f[l]=h[i]
		//若長度增加,r=t,l=t+1; //最大長度增加
		if(l>t)	t=l; 
		f[l]=h[i];//長度l的最長不上升序列結尾是h[i] 
	}
	printf("%d\n",t);
	t=0;memset(f,0,sizeof(f));
	for(int i=1;i<=n;++i){
		int l=0,r=t;//當前系統數範圍
		while(l<=r){//查詢該高度可以拼接的系統,找不到,就新增系統 
			int mid=(l+r)>>1;
			if(f[mid]<h[i]) l=mid+1;
			else r=mid-1;
		}
		if(l>t) t=l;
		f[l]=h[i]; 
	} 
	printf("%d\n",t);
	return 0;
}

相關文章