上升子序列问题
T1朴素的最长严格上升子序列
http://codevs.cn/problem/3955/
给一个数组a1, a2 ... an,找到最长的上升降子序列ab1<ab2< .. <abk,其中b1<b2<..bk。
输出长度即可。
第一行,一个整数N。
第二行 ,N个整数(N < = 1000000)
输出K的极大值,即最长严格上升子序列的长度
5
9 3 6 2 7
3
n²做法:f[i]=j表示以第i个数结尾的最长上升子序列长度为j
#include<cstdio> #include<algorithm> using namespace std; int n,a[5001],f[5001],ans; int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); for(int i=1;i<=n;i++) { int maxn=0; for(int j=1;j<i;j++) if(a[i]>a[j]&&maxn<f[j]) maxn=f[j]; f[i]=maxn+1; ans=max(f[i],ans); } printf("%d",ans); }
nlogn做法:f[i]=表示长度为i的最长上升子序列中,第i个数最小是j,二分查找
#include<cstdio> using namespace std; int n,f[5001],s,x; int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",&x); if(x>f[s]) { f[++s]=x; continue; } int l=0,r=s,k; while(l<=r) { int m=l+r>>1; if(x>f[m]) { l=m+1; k=m; } else r=m-1; } if(f[k+1]>x) f[k+1]=x; } printf("%d",s); }
T2 包含第k个数的最长上升子序列
http://codevs.cn/problem/2188/
LIS问题是最经典的动态规划基础问题之一。如果要求一个满足一定条件的最长上升子序列,你还能解决吗?
给出一个长度为N整数序列,请求出它的包含第K个元素的最长上升子序列。
例如:对于长度为6的序列<2,7,3,4,8,5>,它的最长上升子序列为<2,3,4,5>,但如果限制一定要包含第2个元素,那么满足此要求的最长上升子序列就只能是<2,7,8>了。
第一行为两个整数N,K,如上所述。
接下来是N个整数,描述一个序列。
请输出两个整数,即包含第K个元素的最长上升子序列长度。
8 6
65 158 170 299 300 155 207 389
4
80%的数据,满足0<n<=1000,0<k<=n
100%的数据,满足0<n<=200000,0<k<=n
把k前面大于等于第k个数的都删去,k后面小于等于第k个数的都删去,然后套上面的方法
#include<cstdio> using namespace std; int n,a[200001],f[200001],s,k,x; int main() { scanf("%d%d",&n,&k); for(int i=1;i<=n;i++) scanf("%d",&a[i]); for(int i=1;i<k;i++) if(a[i]>=a[k]) a[i]=-1; for(int i=1;i<=n;i++) { if(a[i]==-1) continue; if(i>k&&a[i]<=a[k]) continue; if(a[i]>f[s]) { f[++s]=a[i]; continue; } int l=0,r=s,p=0; while(l<=r) { int m=l+r>>1; if(a[i]>f[m]) { l=m+1; p=m; } else r=m-1; } if(f[p+1]>a[i]) f[p+1]=a[i]; } printf("%d",s); }
两个错误:
1、基本思路错误。错误代码:
//f[i][j]只会在j时更新,而在j后面与j形成上升子序列的状态不会使j更新。即f[i][j]只对j及其前面的答案正确。故此代码错误
#include<cstdio>
#include<algorithm>
using namespace std;
int n,a[1001],f[1001][1001],k,ans;
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
{
int maxn=0;
for(int j=1;j<i;j++)
if(a[i]>a[j])
{
maxn=max(maxn,f[j][0]);
f[i][j]=f[j][0]+1;
}
f[i][0]=f[i][i]=maxn+1;
}
for(int i=1;i<=n;i++)
ans=max(ans,f[i][k]);
printf("%d",ans);
}
2、二分查找中p没有赋初值0
T3 最长上升子序列划分
http://codevs.cn/problem/4197/
给定一个长度为N(N为偶数)的序列,问能否将其划分为两个长度为N/2的严格递增子序列。
若干行,每行表示一组数据。对于每组数据,首先输入一个整数N,表示序列的长度。之后N个整数表示这个序列。
同输入行数。对于每组数据,如果存在一种划分,则输出“Yes!”,否则输出“No!“。
6 3 1 4 5 8 7
6 3 2 1 6 5 4
Yes!
No!
共两组数据,每组数据行数<=50,0 <= 输入的所有数 <= 10^9
第一组(30%):N <= 20
第二组(70%):N <= 2000
小提示:输入部分可以用while(scanf(“%d”,&n)!=EOF)或者while not eof do read(n)
由观察可以发现,若满足此条件,则序列最长不上升子序列的长度<=2
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; int n,a[2001],f[2001],p; int main() { while(scanf("%d",&n)!=EOF) { for(int i=1;i<=n;i++) scanf("%d",&a[i]); p=0; memset(f,0,sizeof(f)); for(int i=1;i<=n;i++) { int maxn=0; for(int j=1;j<i;j++) if(a[i]<=a[j]) maxn=max(maxn,f[j]); f[i]=maxn+1; } for(int i=1;i<=n;i++) p=max(p,f[i]); if(p>2) printf("No!\n"); else printf("Yes!\n"); } }
注:本解法有误,反例已在评论中指出