P1020导弹拦截(最长上升子序列/最长不降子序列 - O(N2)与O(NlogN)做法)
题意:
有n个导弹分别飞行在不同高度,一颗拦截导弹可以分别拦截几个高度不上升的导弹,问一个拦截导弹可以拦截最多多少个导弹,拦截所有导弹要多少颗拦截导弹
思路:
第一个问题:一个拦截导弹可以拦截最多多少个导弹,这就是一个求最长不降子序列问题
先考虑O(N2)的做法,对于每一个导弹,我们向前遍历之前的每一个导弹,如果小于或者等于其高度,就可以试着更新dp[i]=max(dp[i],dp[j]+1)
dp[i]的每一个元素初始化为1
再来看O(nlogn)的做法,队员前移做法遍历之前每一个元素这里,我们可以选择优化
建立一个树状数组,用数值做下标,维护长度最大值,从后往前循环,每次查询之前已经放到树状数组里面的数中以这个数结尾的最长不上升子序列的长度的最大值,然后把这个最大值+1作为以自己结尾的最长不上升子序列的长度,放到树状数组里面
第二个问题:问最少需要多少个导弹
要用到一个Dilworth定理
Dilworth定理:偏序集的最少反链划分数等于最长链的长度
简单来说就是求一个最长上升子序列,试着来简单证明一下
(1)假设打导弹的方法是这样的:取任意一个导弹,从这个导弹开始将能打的导弹全部打完。而这些导弹全部记为为同一组,再在没打下来的导弹中任选一个重复上述步骤,直到打完所有导弹
(2)假设我们得到了最小划分的K组导弹,从第a(1<=a<=K)组导弹中任取一个导弹,必定可以从a+1组中找到一个导弹的高度比这个导弹高(因为假如找不到,那么它就是比a+1组中任意一个导更高,在打第a组时应该会把a+1组所有导弹一起打下而不是另归为第a+1组),同样从a+1组到a+2组也是如此。那么就可以从前往后在每一组导弹中找一个更高的连起来,连成一条上升子序列,其长度即为K
(3)设最长上升子序列长度为P,则有K<=P;又因为最长上升子序列中任意两个不在同一组内(否则不满足单调不升),则有P>=K,所以K=P
#include<iostream> #include<algorithm> #include<cstring> #include<cstdio> #define lowbit(x) (x&(-x)) using namespace std; const int maxn=100100; int n=0,len; int a[maxn],mx[maxn]; void add(int x,int val) { while(x<=maxn){ mx[x]=max(mx[x],val); x+=lowbit(x); } } int query(int x) { int ans=0; while(x>=1){ ans=max(mx[x],ans); x-=lowbit(x); } return ans; } int main() { while(scanf("%d",&a[++n])!=EOF); n--; int ans=0; for(int i=n;i>=1;i--){ int x=query(a[i])+1; add(a[i],x); ans=max(ans,x); } cout<<ans<<endl; memset(mx,0,sizeof(mx)); ans=0; for(int i=1;i<=n;i++){ int x=query(a[i]-1)+1; add(a[i],x); ans=max(ans,x); } cout<<ans<<endl; return 0; }