1. 程式人生 > 實用技巧 >Dilworth定理的應用和最長??序列

Dilworth定理的應用和最長??序列

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

輸入導彈依次飛來的高度(雷達給出的高度資料是≤50000的正整數),計算這套系統最多能攔截多少導彈,如果要攔截所有導彈最少要配備多少套這種導彈攔截系統。

簡而言之,這個題目就是要求出最長的不遞增序列和不遞增序列的個數。
Dilworth定理 (???什麼東西) ,不遞增序列的個數等於遞增序列的最大長度


所以我們只需要求出不遞增序列的最大長度和遞增序列的最大長度。(問題是這我也不會啊

那麼我們就要學習一下怎麼求最大??序列的長度。

1.O(\(n^2\))

第一問很明顯是求最長不上升子序列,利用dp的思想,我們設f[i]為以第i個數為開頭的最長不上升子序列的長度,然後可以得到這樣一段程式

for(int i=n;i>=1;i--){//注意!!因為它是以i開頭的最長不上升子序列,所以這裡要從n開始迴圈,不然會出現一些奇妙的bug 
        f[i]=1;//以第i個數為開頭的最長不上升子序列的長度至少為1(當這個序列只有它本身這一個數) 
        for(int j=i+1;j<=n;j++){//用前面已經算好的來算現在正在算的這一個 
            if(a[j]<=a[i])//如果a[j]>a[i]的話就不滿足不上升這個要求(畢竟a[j]在a[i]後面) 
                f[i]=max(f[i],f[j]+1); //更新f[i]~記得要+1啊 
        }
        ans1=max(ans1,f[i]);//更新ans1 
    }

2.小於O(\(n^2\))

在樸素n^2演算法中,用f[i]儲存以i結尾的最長不上升子序列長度,如樣例

i 1 2 3 4 5 6 7 8

a 389 207 155 300 299 170 158 65

f 1 2 3 2 3 4 5 6

發現當f的值相同時,越後面的導彈高度越高

用d[i]維護f值為i的最後一個導彈的位置,t記錄當前已經求出最長不升子序列長度

遞推求f時列舉a[d[t]],a[d[t-1]],。。。,a[d[1]]是否≥當前求的導彈高度,是就更新f

while(~scanf("%d",&a[++n]));//讀入資料方法
    n--;//n是導彈數,由於某些原因要--
    for(int i=1; i<=n; i++) {
        f[i]=1;
        for(int j=t; j>0; j--)
            if(a[i]<=a[d[j]])
            {
                f[i]=f[d[j]]+1;
                break;
            }
        t=max(t,f[i]);
        d[f[i]]=i;//簡單的維護過程
        ans=max(ans,f[i]);
    }

3.O(nlogn)求出最長不上升子序列的長度

(即一套系統最多攔截數)(終於到二了)

1.實現方式
首先我們需要一個數組a,儲存從第1個到第n個導彈的高度

然後一個數組d(其實是個棧),儲存不上升序列

把a中的每個元素挨個加到d裡面:

(a中第i個元素為a[i],d長度為len,d中最後一個(也是最小的一個)為d[len])

如果a[i] <= d[len],說明a[i]可以接在d後面(而整個d還是有序的),那就簡單粗暴地把a[i]丟進d:

d[ ++len ] = a[i]

如果a[i] > d[len],說明a[i]接不上
但是我們發揚瞎搞精神:接的上要接,接不上創造條件也要接!

強行把a[i]塞進去:
在d中找到第一個小於a[i]的數,把它踹了,用a[i]代替它!(為什麼正確在下面)

2.為什麼正確
顯然成立

如果y在末尾,由於y < a[i],所以y後面能接的不如a[i]多,y讓位給a[i]可以讓序列更長

如果y不在末尾,那y有生之年都不會再被用到了,直接踹了y就行,y咋樣,who care?

4.最快做法

3方法增加二分 查詢

參考:導彈攔截 題解
P3902 遞增

5、dp做法

#include<bits/stdc++.h>
#define intn long long
#include<iostream>
#include<cstdio>
#include<vector>
#include<string.h>
#include<cmath>
#include<stack>
#define _0for(i, a) for(int i = 0; i < (a); ++i)
#define _1for(i, a) for(int i = 1; i <=(a); ++i)
#define lowbit(x) ((x)&(-x))
#define debug(x) \
(void)(cerr << "L" << __LINE__\
			<< " : " << #x << " = " \
			<< (x) << endl )
using namespace std;
int dp1[200];
int dp2[200];
main(void)
{
	//前邊要求單調遞增dp1  
	//後邊要求單調遞減dp2
	//dp求最長升dp[i]表示以i為結尾的升序列的長度 
	int n;
	cin>>n;

	int ans=0;
	int a[200];
	a[0]=0;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	}
	a[n+1]=0;
	for(int i=1;i<=n;i++)//當前狀態 
	{
		for(int j=0;j<i;j++)//上一個狀態 
		{
			if(a[j]<a[i])
			dp1[i]=max(dp1[i],dp1[j]+1); 
		} 
	}
	for(int i=n;i>=1;i--)//當前狀態 
	{
		for(int j=n+1;j>i;j--)//上一個狀態 
		{
			if(a[j]<a[i])
			dp2[i]=max(dp2[i],dp2[j]+1);
		} 
	}
	for(int i=1;i<=n;i++)
	{
	//	debug(dp1[i]);
	//	debug(dp2[i]);
		ans=max(ans,dp1[i]+dp2[i]-1);
	}
	cout<<n-ans;
}