相信不少小夥伴通常都會在DP裏見到他們—>最長XXX子序列 的身影(垃圾玩意兒)原來我也一直不想要去了解這個東西,可是我仍是(無可奈何)鼓起勇氣去了解了一下這個東西,今天咱們的主題就圍繞着導彈攔截來展開講述吧。(其實不論是什麼序列,到時候改符號就差很少了)c++
首先,這些題題通常就是求一個最長XXX子序列的長度,而導彈攔截呢,就恰好是求長度的一個好問題(我纔不是精心挑選的)因此.....數組
第一個小問,就是求最長不上升子序列長度,而對於第二個小問題,不少人可能沒看明白,其實就是求最長上升子序列的長度,爲何呢?首先,咱們假設最少有K個那什麼玩意兒才能攔截全部導彈,而後對於第i個系統的任意一個高度,第i+1個系統裏確定有比它高的高度(否則你第i個系統早就把沒它高的那玩意兒連上了啊),而後後面的同理,一直這樣下去就好了..........或者有一個Diworth定理:函數
一個序列中降低子序列的最少劃分數個數等於最長上升子序列的長度。
一個序列中上升子序列的最少劃分數個數等於最長降低子序列的長度。
每句中的先後二者互爲偏序關係。
這個最暴力的方法,就是咱們外層循環枚舉一個子序列的結尾,而後內層從1到i-1,枚舉這個子序列的倒數第二個,若是接的上,就取最優值,否則.......就啥也不幹。spa
1 #include <bits/stdc++.h> 2 int a[100010]; 3 int num[100010]; 4 int dp[100010]; 5 int n=0; 6 int x; 7 int ans=0,maxn=0; 8 using namespace std; 9 int main() 10 { 11 while(cin>>x) 12 num[++n]=x; 13 for(int i=1;i<=n;i++)//由於開始每個的序列長度都是本身 14 a[i]=dp[i]=1; 15 16 for(int i=1;i<=n;i++) 17 { 18 for(int j=1;j<i;j++)//枚舉結尾和它前面的玩意兒 19 { 20 if(num[i]<=num[j]) 21 a[i]=max(a[i],a[j]+1);//替換 22 if(num[i]>num[j]) 23 dp[i]=max(dp[i],dp[j]+1); 24 } 25 } 26 27 for(int i=1;i<=n;i++)//求最長長度 28 { 29 ans=max(ans,a[i]); 30 maxn=max(maxn,dp[i]); 31 } 32 33 cout<<ans<<endl; 34 cout<<maxn; 35 return 0; 36 }
嗯,交上去就TLE了,看看N,絕對要炸啊,因此,聰明的大佬們果真又........設計
咱們開一個數組dp,而後從a數組(存導彈高度的)1遍歷到n指針
遇到比dp數組最後一個小的,就接上去code
dp[++len]=a[i]
否則,就只能高鐵霸座了,找到第一個小於a[i],的數,直接佔位子。可是問題來了,咱們怎麼找這個數,通常的人都會用而分,可是,像我這種聰明人就選擇用STL裏面自帶的函數啦blog
假設查找x,ci
lower_bound是找出一個序列中第一個大於等於x的數get
upper_bound是找出一個序列中第一個大於x的數(親兄弟啊)
然而日常這倆玩意兒都是針對升序的(那還有什麼用啊)
可是根據編C++的人的尿性,確定會有自定義函數的啊,沒錯,因此.....
lower_bound(dp+1,dp+1+n,x,cmp);
是否是和sort很像,只須要本身打一個比較函數就能夠了(可是我懶,因此.....)
lower_bound(dp+1,dp+1+n,x,greater<int>());
和上面差很少,專爲懶人設計你值得擁有
但你要知道,它返回的倒是個指針(指針什麼的最煩了)
因此,你用指針的話.......
int *p = lower_bound(本身想象);
或者直接減去數組開頭指針,差就是下標
int p=lower_bound(本身想象)-a;
而後要注意,dp裏存的並非最大不上升子序列,由於它每時每刻(簡單快樂)都在更新嗯,就這樣,讓咱們來愉快的模擬樣例吧
a={0,389,207,155,300,299,170,158,65} a[i]=389 dp={0,389} len=1 dp[len=389]
第一個直接進去
a={0,389,207,155,300,299,170,158,65} a[i]=207 dp={0,389,207} len=2 dp[len]=207
而後,下一個比這一個小,加進去
a={0,389,207,155,300,299,170,158,65} a[i]=155 dp={0,389,207,155} len=3 dp[len]=155
繼續加
a={0,389,207,155,300,299,170,158,65} a[i]=300 dp={0,389,300,155} len=3 dp[len]=155
哦吼,比這個大了,咱們只能踢人了
a={0,389,207,155,300,299,170,158,65} a[i]=299 dp={0,389,300,299} len=3 dp[len]=299
繼續踢人
a={0,389,207,155,300,299,170,158,65} a[i]=170 dp={0,389,300,299,170} len=4 dp[len]=170
加進去
a={0,389,207,155,300,299,170,158,65} a[i]=158 dp={0,389,300,299,170,158} len=5 dp[len]=158
加進去
a={0,389,207,155,300,299,170,158,65} a[i]=65 dp={0,389,300,299,170,158,65} len=6 dp[len]=65
加進去
好啦,模擬完了(好水啊)
最後貢獻一下個人代碼吧
1 #include<bits/stdc++.h> 2 using namespace std; 3 int len1=1,len2=1; 4 int a[100005]; 5 int d1[100005],d2[100005]; 6 int n; 7 int main() 8 { 9 while(cin>>a[++n]); 10 n--; 11 d1[1]=a[1]; 12 d2[1]=a[1]; 13 for(int i=2;i<=n;i++) 14 { 15 //最長不上升子序列長度 16 if(a[i]<=d1[len1]) 17 d1[++len1]=a[i];//加進去 18 else *upper_bound(d1+1,d1+1+len1,a[i],greater<int>())=a[i];//踢人 19 //最長上升子序列的長度 20 if(a[i]>d2[len2]) 21 d2[++len2]=a[i]; 22 else *lower_bound(d2+1,d2+1+len2,a[i])=a[i]; 23 } 24 cout<<len1<<endl<<len2; //輸出就好 25 return 0; 26 }
到後面,又出現了求每一序列中長度爲m的字典序最小的子序列(真的很噁心)哎,仍是來了解一下吧
1 #include<bits/stdc++.h> 2 using namespace std; 3 int dp[10005],a[10005]; 4 int maxn,q,n,m; 5 int main() 6 { 7 scanf("%d",&n); 8 for(int i=1;i<=n;i++) 9 { 10 scanf("%d",&a[i]); 11 dp[i]=1; 12 } 13 scanf("%d",&m); 14 for(int i=n-1;i>=1;i--) 15 { 16 for(int j=i+1;j<=n;j++) 17 if(a[j]>a[i]&&dp[j]+1>dp[i]) 18 dp[i]=dp[j]+1; 19 maxn=max(maxn,dp[i]);//加了一個求這個序列中最長序列的長度 20 } 21 //以上仍是O(n*n)的操做 22 for(int i=1;i<=m;i++) 23 { 24 scanf("%d",&q); 25 if(q>maxn)//若是沒有這麼長的,就表明找不到了 26 { 27 puts("Impossible"); 28 continue; 29 } 30 int pre=-1e9; 31 for(int j=1;j<=n;j++) 32 { 33 if(dp[j]>=q&&a[j]>pre)//若是如今找到的以這一個爲 開頭的最長上升子序列的長度夠的話 34 //而且比以前那一個大(知足上升子序列 35 { 36 printf("%d ",a[j]);//打印 37 pre=a[j]; 38 q--;//須要的長度能夠減一 39 if(q==0)//打印完成 40 break; 41 } 42 } 43 } 44 return 0; 45 }