cdoj dp专题的N题----拦截导弹
链接: http://acm.uestc.edu.cn/#/contest/show/65
这是一道很经典的dp,题意简述:给出n个数,求出单调递增子序列的长度,并输出其中字典序最小的那个。
这道题范围为10e5,那么O(n^2)算法必然跑不过,只能采取O(nlogn)的解决方法。
先还是讲一下O(n^2)的做法。
我们来看一个例子 1 3 6 2 7,那么我们可以定义dp[i]为以a[i]为结尾单调递增子序列的长度,因为我们可以保证最后一个可以取到,dp[i]=dp[j]+1 (其中j<i&&a[j]<a[i],其中dp[j]是最大的).
dp[0]=1;
dp[1]=2;
dp[2]=3
dp[3]=2;
dp[4]=4;
很显然len=4;
那么我们来写一下代码:
int t;
dp[0]=1;
for(int i=1;i<n;i++)
{
t=0;
for(int j=0;j<i;j++)
{
if(a[j]<a[i])
t=max(t,dp[j]);
}
dp[i]=t+1;
}
for(int i=0;i<n;i++)
printf("%d\n",dp[i]);
这样就求出了0到i的单调递增长度,那么我们现在要解决的就是输出路径
按照往常搜索的输出路径写法,有两种写法,第一种,pos[i]表示当前路径上的第i个点,其一般存储的是原数组的下标,
pre[j]表示的是原数组第j个数的前一个是路径上的哪个点的在原数组中的下标,那么pre[pos[i]]的含义就是路径i的前一个点的在原数组的下标。
继续给出代码
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define N 100005
using namespace std;
int a[N]={-5,1,4};
int dp[N];
int pos[N];
int pre[N];
void print(int x){
if(x==0) return;
print(pre[x]);
printf("%d ",a[x]);
}
int main(){
int t,len=0;
for(int i=1;i<=3;i++)
{
t=0;
for(int j=1;j<i;j++)
{
if(a[j]<a[i])
t=max(t,dp[j]);
}
dp[i]=t+1;
pos[t+1]=i;
if(t>0)
pre[i]=pos[t];
len=max(t+1,len);
}
printf("%d\n",len);
print(pre[pos[len]]);
printf("%d\n",a[pos[len]]);
}
现在我们考虑O(nlogn)的做法,我们需要用二分的写法
直接用lower_bound了,直接上关键代码了。
b[]记录的是dp[i]的最小的值,然后二分就行了
还是下一周再补上吧,不然就雷同代码le2333