题解:P1020 [NOIP 1999 提高组] 导弹拦截

题目传送门

显然,一个系统能够防御的导弹个数就是这个导弹高度的最长不升子序列。用 dp 就可以把这部分解决,可以得到 \(80\) 分。

第一问主体代码:

c[len] = 5e4+7;
for(int i = 1;i<=n;i++){
    if(a[i]<=c[len]){//如果能加,就直接加
        c[++len] = a[i];
    }else{
        int l = 1,r = len;
        int ans = 0;
        while(l<=r){//二分查找第一个小于这个a[i]的地方,插入
            int mid = (l+r)>>1;
            if(c[mid]<a[i]){
                ans = mid;
                r = mid-1;
            }else{
                l = mid+1;
            }
        }
        c[ans] = a[i];
    }
}
cout<<len<<endl;

上面是第一问,现在我们看看第二问。

第二问说,要用多少个系统才能把所有导弹拦截下来。

这里就要引入我们的 Dilworth 定理了:

把整个序列划分成若干个最长不升自序列的个数等于这个序列的最长严格上升子序列。

证明略。

于是,第二问主体代码就出来了:

for(int i = 1;i<=n;i++){
    if(a[i]>c[len]){
        c[++len] = a[i];
    }else{
        int id = lower_bound(c+1,c+1+len,a[i])-c;
        c[id] = a[i];
    }
}
cout<<len;

把这两份代码拼起来,我们就能拿到 \(200pts\) 了!

AC 代码:

#include <iostream>
#include <algorithm>
#define int long long
using namespace std;

int n;
int a[100005];
int c[100005];
int len;
bool flag[100005];
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    int x;
    while(cin>>x){
        a[++n] = x;
    }
    c[len] = 5e4+7;
    for(int i = 1;i<=n;i++){
        if(a[i]<=c[len]){
            c[++len] = a[i];
        }else{
            int l = 1,r = len;
            int ans = 0;
            while(l<=r){
                int mid = (l+r)>>1;
                if(c[mid]<a[i]){
                    ans = mid;
                    r = mid-1;
                }else{
                    l = mid+1;
                }
            }
            c[ans] = a[i];
        }
    }
    cout<<len<<endl;
    len = 0;//清空
    c[len] = 0;//最长严格上升子序列c[0]应该为0,否则第一个点加不进去
    for(int i = 1;i<=n;i++){
        if(a[i]>c[len]){
            c[++len] = a[i];
        }else{
            int id = lower_bound(c+1,c+1+len,a[i])-c;
            c[id] = a[i];
        }
    }
    cout<<len;
    return 0;
}
posted @ 2025-10-01 15:10  GeorgeDeng114514  阅读(20)  评论(0)    收藏  举报