luogu P1020 导弹拦截 x
首先上题目~
luogu P1020 导弹拦截
题目描述
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
输入输出格式
输入格式:
一行,若干个正整数最多100个。
输出格式:
2行,每行一个整数,第一个数字表示这套系统最多能拦截多少导弹,第二个数字表示如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
输入输出样例
389 207 155 300 299 170 158 65
6 2
思路:From 题解大大!~~~
首先分析题意,第一个问题就是最长不升子序列(好多题解都说反了→_→)
第二个问题,等价于求最长上升子序列
给一个严密的数学证明【证明部分引自CSDN】
这里我们要介绍一个很优美的定理~(优美?)
Dilworth定理:
对于一个偏序集,最少链划分等于最长反链长度。
Dilworth定理的对偶定理:
对于一个偏序集,其最少反链划分数等于其最长链的长度。
也就是说把一个数列划分成最少的不升子序列的数目就等于这个数列的最长上升子序列的长度。
下面来说说这个定理是怎么来的:
偏序集的定义:偏序是在集合X上的二元关系≤(这只是个抽象符号,不是“小于或等于”,它满足自反性、反对称性和传递性)。即,对于X中的任意元素a,b和c,有:
(1)自反性:a≤a;
(2)反对称性:如果a≤b且b≤a,则有a=b;
(3)传递性:如果a≤b且b≤c,则a≤c 。
带有偏序关系的集合称为偏序集。
首先令(X,≤)是一个偏序集,对于集合中的两个元素a、b,如果有a≤b或者b≤a,则称a和b是可比的,否则a和b不可比。
在这个例子(反链)中元素Ri<=Rj是指(i<=j) and (ai>=aj)
一个反链A是X的一个子集,它的任意两个元素都不能进行比较。
一个链C是X的一个子集,它的任意两个元素都可比。
【定理】
在X中,对于元素a,如果任意元素b,都有a≤b,则称a为极小元。
定理1:令(X,≤)是一个有限偏序集,并令r是其最大链的大小。则X可以被划分成r个但不能再少的反链。
其对偶定理称为Dilworth定理:
令(X,≤)是一个有限偏序集,并令m是反链的最大的大小。则X可以被划分成m个但不能再少的链。
虽然这两个定理内容相似,但第一个定理证明要简单一些。此处就只证明定理1。
证明:设p为最少反链个数
(1)先证明X不能划分成小于r个反链。由于r是最大链C的大小,C中任两个元素都可比,因此C中任两个元素都不能属于同一反链。所以p>=r。
(2)设X1=X,A1是X1中的极小元的集合。从X1中删除A1得到X2。注意到对于X2中任意元素a2,必存在X1中的元素a1,使得a1<=a2。令A2是X2中极小元的集合,从X2中删除A2得到X3……,最终会有一个Xk非空而Xk+1为空。于是A1,A2,…,Ak就是X的反链的划分,同时存在链a1<=a2<=…<=ak,其中ai在Ai内。由于r是最长链大小,因此r>=k。由于X被划分成了k个反链,因此r>=k>=p。
(3)因此r=p,定理1得证。
【解决】
要求最少的覆盖,按照Dilworth定理
最少链划分 = 最长反链长度
所以最少系统 = 最长导弹高度上升序列长度。
【引用部分结束】
dalao原话是酱紫哒~
然后。。。由于不知道数据范围。。。我抱着书啃了好久,写了个O(nlogn)的算法。。。事后才知道貌似n不大于15以及这个算法超出了NOIP的范围。。。
不过,还是给大家介绍一下吧(以最长上升子序列为例)
由常识(额...什么常识????)可得,在同一个范围内,有两个长度相等的子序列,那么末尾元素较小的那一个,在连接后面的元素时更有优势,因此,如果我们只保留末尾元素最小的那个状态,不会丢掉最优解;
这样我们可以令状态d(i)=长度为i+1的子序列末尾最小元素(如果不存在长度为i+1的上升子序列则为INF)
然后根据上面的推理,我们很容易得到,除掉后面那一串INF外,d是单调递增的
需要注意的是,我们所计算的d是在动态改变的(这也就是我前面强调同一个范围内的原因)
也就是说,我们依次考虑前i个数,随着i的不断增大,d也是在不断改变的
明显,边界条件dp[0]=0;
那么,我们从前到后考虑原数列中的每一个元素a[j]。如果有dp(i-1)<a[j],那么就意味着,a[j]可以加在这个长度为i的这个数列的最后;那么所有满足条件的a[j]中最小的一个,就是dp[i]的值(长度为i+1的上升子序列中的最小末尾元素)
如果这么朴素的实现,也可以在O(n^2)的时间内完成
我们把这个思路转一下,由于d是递增的,所以对于任何一个a[j],它只会有一次被放入dp数组中(以后放入的,一定比它大)
那么应该放在哪里呢?
我们只需要在dp数组中,找到满足dp(k)>=a[j]的第一个下标k,更新d(k)=a[j]即可
为什么这样做是正确的呢?
首先,如果a[j]能够合法地替换掉那个长度为k+1的子序列的末尾元素,也就是a[j]>d[k-1]的话,那么这样做的正确性是很明显的;
如果a[j]<=d[k-1]呢,那么就与k为第一个满足d(k)>=a[j]的下标这一先决条件不符
那么,我们就可以使用二分查找来进行优化,最后输出满足d(i)<INF的最大i值所对应的子序列长(也就是i+1)即可
第一个是自己写一个二分查找,第二个就是
阅读建议:先看后半段求最长上升子序列的,然后再看前半段求不升子序列的,便于理解
代码酱来也~
#include <iostream> #include <cstdio> #include <algorithm> using namespace std; const int M = 5e5 +233; int cv[M]; int dp[M]; int main() { int tails,lefts,rights; int i=0,j=0; int QwQ; while(scanf("%d",&QwQ)!=EOF) { ///原来EOF输入需要Ctrl+Z!QwQ ///我说为什么不出数QwQ i++; cv[i]=QwQ; } /* for(j=1;j<=i;j++) printf("%d ",cv[j]); */ ///i--;///因为最后的时候输入了一个EOF,需要将其删去,所以i-- dp[1]=cv[1];///dp tails=1;///设置队尾(队列) for(j=2;j<=i;j++) {///最长不下降序列 lefts=1; rights=tails; while(lefts<=rights)///二分? { int mids=(lefts+rights)/2; if(dp[mids] >= cv[j]) lefts=mids+1; else rights=mids-1; } dp[lefts]=cv[j];///进行修改 if(lefts > tails) tails++;///入队 } printf("%d\n",tails);///换行符!!!血的教训啊!!! for(j=1;j<=i;j++)///清空! dp[j]=0; ///又来一遍hhhh tails=1; dp[1]=cv[1]; for(j=2;j<=i;j++) {///最长上升序列 lefts=1; rights=tails; while(lefts<=rights)///二分w { int mids=(rights+lefts)/2; if(dp[mids] < cv[j]) lefts=mids+1;///这一次求的是最长上升序列w else rights=mids-1; } dp[lefts]=cv[j];///进行修改 if(lefts > tails) tails++;///入队 } printf("%d\n",tails); return 0; }
#include<iostream> using namespace std; int h[1001],ht[1001],f[1001]; int ans=0,l=1; int main() { while(cin>>h[l]) l++; f[0]=0x7fffffff; for(int i=1; i<l; i++) for(int j=ans; j>=0; j--) if(f[j]>=h[i]) { f[j+1]=h[i]; ans=max(ans,j+1); break; } cout<<ans; ans=0; for(int i=1; i<l; i++) { for(int j=0; j<=ans; j++) { if(ht[j]>=h[i]) { ht[j]=h[i]; break; } } if(ht[ans]<h[i]) ht[++ans]=h[i]; } cout<<endl<<ans; return 0; }
End.