题目大意:最大上升子列
LIS最大上升子列
朴素算法:O(n*n)
子问题:每次找到一个从前面到以i位置为结束的上升子列,而i位置要往前面找一个最大的上升子列dp[j],进而把问题规模缩小到j
状态:dp[i]->以i位置元素为结束的上升子列的长度
转移方程:dp[i]=max(dp[i],dp[j]+1) dp[j]->表示在j段中以j为结束的上升子列 (i>j,data[i]>data[j])
dp[]要初始化到1,因为因为每个位置最小上升子列都是1即它本身
#include<stdio.h>
#define MAX 100010
int data[MAX],dp[MAX];
int main()
{
int i,j,n,m=0;
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%d",&data[i]);
dp[i]=1;
}
for(i=1;i<=n;i++)
{
for(j=1;j<i;j++) //这里注意要到i-1位置,因为如果遍历到i相当于子列尾部有两个data[i]错误,从结果上看到j==i一定有dp[i]<dp[j]+1
{
if(data[i]>data[j] && dp[i]<dp[j]+1)
dp[i]=dp[j]+1;
}
if(m<dp[i])
m=dp[i];
}
printf("%d\n",m);
}
/*
LIS最长上升子列
二分算法:O(n*logn)
*/
int find(int num,int left,int right) //通过二分法找到一个刚好比num大的数
{
int mid;
while(left<=right)
{
mid=(left+right)/2;
if(dp[mid]<=num) left=mid+1;
else right=mid-1;
}
return left;
}
int DP(int n)
{
int i,len,s;
dp[1]=data[1];
len=1;
for(i=2;i<=n;i++)
{
if(data[i]>dp[len])
{
len++;
dp[len]=data[i];
}
else//通过二分查找一个刚好比data[i]大的数,然后替换它,动态地使整个子列挨得更紧
{
s=find(data[i],1,len);
dp[s]=data[i];
}
}
return len;
}
int main()
{
int i,n;
scanf("%d",&n);
for(i=1;i<=n;i++)
scanf("%d",&data[i]);
printf("%d\n",DP(n));
return 0;
}
7
1 7 3 5 9 4 8
添加长度后:1 7
##更替前:1 7
##更替后:1 3
添加长度后:1 3 5
添加长度后:1 3 5 9
##更替前:1 3 5 9
##更替后:1 3 4 9
##更替前:1 3 4 9
##更替后:1 3 4 8
4
第二种算法可以总结成一个模板,我觉得这种思想是贪心思想,每次都使整个子列更紧密,为什么要这样做呢?因为前面挨得越紧就相当于前面的最大一个数可能越小,导致整个数列能够把最大值往下降,子列就更有变长的希望。