题目大意就不要介绍了吧。
为了拦截所有的导弹,我们的第一想法就是贪心,就是觉得要每一个导弹防御系统尽可能拦截多的拦截更多的导弹但是实际上这种想法是不对的,下面就可以给出一个反例:
导弹依次飞来的高度依次是 6 4 1 7 3 2.
如果你让拦截6之后还尽可能拦截更多的话,那就是6 4 3 2
剩下1 7
而实际上我们只需要6 4 1 7 3 2两个系统就可以了。
所以如此贪心的思路是不对的。
下面借用Teilwall的一句话:
求最长上升子序列:
给定排好序的一堆数列中,求其的LIS长度。它的LIS长度就是它非上升子序列的个数。
WHY?
其实自己模拟一下就可以发现:计算出第一组非上升子序列,它的最后一个数一定是这组数列的最小的一个数;第二组非上升子序列的最后一个数就一定会是剩下的 数中最小的一个..........哪么,上升子序列的长度是多少,就一定可以排出多少组非上升子序列~但每一组非上升子序列的最后一个数并不一定就是所 求上升子序列的里的数,但每一组一定有一个数是所求最长上升子序列里的数.........
虽然我到现在还是不太理解这段话的含义,但是我依然觉得很有道理:
到了9号了,终于是可以找到一点上面的话的理论点的证明了。下面是两个重要定理:
定理1 令(X,≤)是一个有限偏序集,并令r是其最大链的大小。则X可以被划分成r个但不能再少的反链。
其对偶定理称为Dilworth定理:
定理2 令(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非空而X(k+1)为空。于是A1,A2,…,Ak就是X的反链的划分,同时存在链a1<=a2<=…<=ak,其中ai在Ai内。由于r是最长链大小,因此r>=k。由于X被划分成了k个反链,因此r>=k>=p。因此r=p,定理1得证。
定理及证明转自http://blog.sina.com.cn/s/blog_9634532001019znf.html
这样一来我们就的确吧两个思路转化为了一个可以理论性证明的两个相反的过程。相当于:
(<)就是偏序关系,最长链就是最长升序子序列,而它的反链的最少个数就是满足(>=)的最少链的个数。
从这段话Teilwall就帮我们给了两种方法:
第一种: 直接求出每一个导弹拦截系统当前所能拦截的最大值并把它之后所有能拦截的都拦截掉,注意这里并不求最多可以拦截多少个,而是一旦可以拦截,就拦截掉并在之后寻找更低的。按此思路上面的例子就有正确解了6>4>1,一个7>3>2一个,所以共两个。
第二种方法: 给定排好序的一堆数列中,求其的LIS长度。它的LIS长度就是它非上升子序列的个数。 从这句话中我们就可以知道我们就可以直接求原来序列的最长升序子序列 ,它的长度就是原题的解。待吾等慢慢捉摸。
从上面两种方法来看我们似乎就从传统的DP(DP[i] = {DP[j]+1 | a[j] < a[i]})和 nlogn的算法中概括出了一个新的求最长升序子序列的方法。先看看第一种方法是如何实现的吧,定义一个vis[]数组
1 int vis[],a[],ans; 2 for(int i=0;i<n;i++) 3 { 4 if(!vis[i]) 5 { 6 ans++; 7 int min = a[i]; 8 for(int j=i+1;j<n;j++) if(!vis[j] && a[j] < min) 9 { 10 vis[j] = 1; 11 min = a[j]; 12 } 13 } 14 } 15 printf("%d\n",ans);
这样ans就是最后的结果(初始化为0),我们可以注意到它比传统的Dp效率要高,事实证明的确如此,因为他并不像DP要对每一个i都要扫描一遍0~i-1,因为如果i已经被标记了,那就不需要往后查找了。这个方法复杂度比DP的方法效率要高很多,特别是如果数据较大的话,相信这个就会好了很多,当然用哪个nlogn的方法会更好一些,前提是如果理解的话。因为这个在最坏情况下(也就是如果原来序列已经是升序),那它的复杂度也会是O(n^2),所以慎用。
另外这道题我在其他人那里又得到了一种新的解法,吧到当前时,所有导弹系统还能挡住的最高的高度记录下来,存放在一个数组里面,这样的话如果当前的导弹比这个数组里面所有的数都要大的话,那肯定就没有系统可以挡住他,那就必须另外另外添加一个系统,并把这个系统可以达到的最大值(也就是当前高度)放进数组;如果当前导弹的高度比数组里的某一个要小的话,那就找到比它大但是最接近的那个值,用它来替换掉。这样也就是为了既能够拦截这个导弹,又不浪费可以打到后面更高的系统最好的解。
如果认真想想的话,那刚刚最后一种思路其实就是求序列最长升序列的一种具体的表现,把理论性的证明实例化了。
下面给出上面思路的三种代码:
1 /* 2 第一种思路,直接标记小的 3 */ 4 #include <stdio.h> 5 #include <string.h> 6 int vis[1001], a[1001]; 7 int N; 8 int main() 9 { 10 while(~scanf("%d", &N)) 11 { 12 memset(vis,0,sizeof(vis)); 13 for(int i=1;i<=N;i++) 14 { 15 scanf("%d", &a[i]); 16 } 17 int ans = 0; 18 for(int i=1;i<=N;i++)if(!vis[i]) 19 { 20 vis[i] = 1; 21 ans++; 22 int minn = a[i]; 23 for(int j=i+1;j<=N;j++) if(!vis[j] && a[j] < minn) 24 { 25 vis[j] = 1; 26 minn = a[j]; 27 } 28 } 29 printf("%d\n", ans); 30 } 31 return 0; 32 }
下面是求最长升序子序列DP的做法
1 #include <stdio.h> 2 #include <string.h> 3 #define MAX(a,b) (a>b)?a:b 4 int a[1001],f[1001]; 5 6 int main() 7 { 8 int n; 9 while(~scanf("%d", &n)) 10 { 11 for(int i=1;i<=n;i++) 12 { 13 scanf("%d", &a[i]); 14 } 15 int Max = 0; 16 memset(f,0,sizeof(f)); 17 for(int i=1;i<=n;i++) 18 { 19 int k=0; 20 for(int j=1;j<i;j++)if(a[j] < a[i]) 21 { 22 k = MAX(f[j], k); 23 } 24 f[i] = k+1; 25 Max = MAX(Max, f[i]); 26 } 27 printf("%d\n", Max); 28 } 29 return 0; 30 }
下面是nlogn的解法(实现见http://www.cnblogs.com/gj-Acit/p/3236384.html)
1 #include <stdio.h> 2 #include <string.h> 3 #define MAX(a,b) (a>b)?a:b 4 int a,f[1001]; 5 6 int main() 7 { 8 int n; 9 while(~scanf("%d", &n)) 10 { 11 int ans = 0; 12 memset(f,0,sizeof(f)); 13 for(int i=1;i<=n;i++) 14 { 15 scanf("%d", &a); 16 int l=0, r = ans, mid; 17 while(l < r)//二分 18 { 19 mid = (l+r)/2; 20 if(f[mid] <= a) l = mid+1; 21 else r = mid; 22 } 23 f[l] = a; 24 if(l == ans) ans ++; 25 } 26 printf("%d\n", ans); 27 } 28 return 0; 29 }