相信很多小伙伴一般都会在DP里见到他们—>最长XXX子序列 的身影(垃圾玩意儿)原来我也一直不想要去了解这个东西,但是我还是(迫不得已)鼓起勇气去了解了一下这个东西,今天我们的主题就围绕着导弹拦截来展开讲述吧。(其实不管是什么序列,到时候改符号就差不多了)
首先,这些题题一般就是求一个最长XXX子序列的长度,而导弹拦截呢,就刚好是求长度的一个好问题(我才不是精心挑选的)所以.....
第一个小问,就是求最长不上升子序列长度,而对于第二个小问题,很多人可能没看明白,其实就是求最长上升子序列的长度,为什么呢?首先,我们假设最少有K个那什么玩意儿才能拦截所有导弹,然后对于第i个系统的任意一个高度,第i+1个系统里肯定有比它高的高度(不然你第i个系统早就把没它高的那玩意儿连上了啊),然后后面的同理,一直这样下去就行了..........或者有一个Diworth定理:
一个序列中下降子序列的最少划分数个数等于最长上升子序列的长度。
一个序列中上升子序列的最少划分数个数等于最长下降子序列的长度。
每句中的前后两者互为偏序关系。
O(N^2)求长度
这个最暴力的方法,就是我们外层循环枚举一个子序列的结尾,然后内层从1到i-1,枚举这个子序列的倒数第二个,如果接的上,就取最优值,不然.......就啥也不干。
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,绝对要炸啊,所以,聪明的大佬们果然又........
O(nlogn)求长度
我们开一个数组dp,然后从a数组(存导弹高度的)1遍历到n
遇到比dp数组最后一个小的,就接上去
dp[++len]=a[i]
不然,就只能高铁霸座了,找到第一个小于a[i],的数,直接占位子。但是问题来了,我们怎么找这个数,一般的人都会用而分,但是,像我这种聪明人就选择用STL里面自带的函数啦
lower_bound & upper_bound
假设查找x,
lower_bound是找出一个序列中第一个大于等于x的数
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 }